Quick search

Pong Game Tutorial(翻訳済み)

Introduction(前書き)

ようこそポンのチュートリアルへ

このチュートリアルでは、Kivyを使ってポンゲームを作成する方法を教えます。私たちは、アプリケーションを作成で説明したようなアプリケーションの基礎(Create an application(アプリケーションを作成する))から始めましょう。そして、プレイすることが可能なポンゲームを各工程に沿って説明します。

../_images/pong.jpg

まず、チュートリアルを開始する前に以下をチェックしてください:

プログラミングガイドを読んで、Widgetの基本概念((A Simple Paint App(翻訳済み))とkv Languageの基本概念(Kv language(翻訳済み))の両方を”理解しているならば、最初の2ステップをスキップして、ステップ3に進んでください。

注釈

Kivyのexamplesディレクトリ配下の tutorials/pong/ に各ステップのソースコードと全体のソースコードのファイルがあります。

準備はいいですか? すてき、では始めましょう!

Getting Started(はじめに)

Getting Started(はじめに)

シンプルなKivyアプリを準備して起動しましょう。ゲーム用のディレクトリと、main.py ファイルを作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from kivy.app import App
from kivy.uix.widget import Widget


class PongGame(Widget):
    pass


class PongApp(App):
    def build(self):
        return PongGame()


if __name__ == '__main__':
    PongApp().run()

アプリケーションを実行してください。この時点では黒い画面が表示されます。私たちがやったことは、非常にシンプルなKivyクラスの :class: ~kivy.app.App を作成することです。これは、``PongGame``Widgetクラスを作成しインスタンスを作成します。これをアプリケーションUIのルート要素として返します。また、この時点でウィジェットの階層ツリーとして想像してください。Kivyはこのウィジェットツリーをデフォルトの画面に配置します。次のステップでは、PongGameウィジェットの外観を定義し背景とスコアを描画します。

Add Simple Graphics(シンプルなグラフィックの追加)

pong.kvをつくります

.kvファイルを使用して``PongGame``クラスのルック&フィール(見ため・感じ)を定義します。:class:の`~Kivy.app.App`は`PongApp`という名前なので、同じディレクトリ内に``pong.kv``を作ることで、アプリケーションが実行されるときに自動的に読み込まれます。``pong.kv`` という名前のファイルを作成して以下の内容を追加してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#:kivy 1.0.9

<PongGame>:    
    canvas:
        Rectangle:
            pos: self.center_x - 5, 0
            size: 10, self.height
            
    Label:
        font_size: 70  
        center_x: root.width / 4
        top: root.top - 50
        text: "0"
        
    Label:
        font_size: 70  
        center_x: root.width * 3 / 4
        top: root.top - 50
        text: "0"

注釈

よくある間違い:kvファイルの名前はpong.kvのようにアプリの名前と一致する必要があります(例: PongApp(Appの終わりの前の部分)。

アプリを起動すると、真ん中に縦棒が表示されてプレーヤーの得点が2つ表示されます。

Explaining the Kv File Syntax(Kvファイルの構文の説明)

次のステップに進む前に、作成したkvファイルの内容を詳しく見て、何が起こっているのか理解したいと思っているかもしれません。何が起きているのかを理解しているならば次のステップに進めます。

最初の行には次のものがあります:

#:kivy 1.0.9

この最初の行はすべてのkvファイルに必要です。「#:kivy」の後ろにスペースとKivyのバージョンがあるはずです。(Kivyで必要なバージョンを確認したり、後で後方互換性を扱えます)。

その後、すべての「PongGame」インスタンスに適用されるルールの定義を開始します。

<PongGame>:
    ...

Pythonと同様に、kvファイルはインデントを使用してネストされたブロックを定義します。 「<」と「>」文字の内部にクラス名で定義されたブロックは、:class:`~kivy.uix.widget.Widget`ルールです。指定されたクラスのインスタンスに適用されます。今回の例では「PongGame」を「Widget」に置き換えた場合、すべてのWidgetインスタンスは、これらのルールをすべてのWidgetインスタンスに対して定義するため、中に縦線と2つのLabelウィジェットを持ちます。

ルールセクションの中でさまざまなブロックを追加して適用されるwidgetのスタイルと内容を定義します。以下が可能です:

  • propertyの値をセットします。

  • widgetの子を追加します

  • widgetのレンダリング方法を定義するGraphics命令を追加できる「canvas」セクションを定義します。

「<PongGame>」ルール内の最初のブロックは canvas ブロックです:

<PongGame>:
    canvas:
        Rectangle:
            pos: self.center_x - 5, 0
            size: 10, self.height

このキャンバスブロックは、「PongGame」widgetがいくつかのgraphicsの初期値を描くべきです。この場合、キャンバスにrectangle(長方形)を追加します。rectangleのpos(位置)は、widgetの横方向の中心から5ピクセル左に設定し、yは0に設定します。長方形のサイズは幅が10ピクセル、widgetの高さはwidget自身の高さに設定されます。このようにgraphicsを定義するのは、式で使用されるwidgetのpropertyが変更されたときにレンダリングされた長方形を自動的に更新するためです。

注釈

アプリケーションウィンドウのサイズを変更して何が起こるかを確認してください。UI全体が自動的にリサイズされます。 Windowの標準的な動作は、「size_hint」propertyに基づいて要素のサイズを変更することです。デフォルトwidgetのsize_hintは(1,1)です。つまりxy方向の両方に100%伸張され、利用可能なスペースを埋めることを意味します。長方形とcenter_xの頂点とサイズとスコアラベルの上部は「PongGame」クラスのコンテキスト内で定義されていたので、対応するwidgetのpropertyが変更されると、これらのpropertyは自動的に更新されます。 Kv languageを使用すると、自動的にpropertyがバインドされます。 :)

最後の2つのセクションはかなり類似してます。それぞれが「PongGame」に子widgetとして「Label widget」を追加します。今のところ、両方のテキストには 「0」が設定されています。ロジックが実装されたら、実際のスコアに設定しますが、font_sizeを大きく設定しているのでLabelはすでに大きく見えていて、root widgetに対して相対的に配置されています。「root」キーワードは、ルールが適用される親/root widget(この場合は「PongGame」)を参照するために子ブロック内で使用できます。

<PongGame>:
    # ...

    Label:
        font_size: 70
        center_x: root.width / 4
        top: root.top - 50
        text: "0"

    Label:
        font_size: 70
        center_x: root.width * 3 / 4
        top: root.top - 50
        text: "0"

Add the Ball(ボールを追加)

Add the Ball(ボールを追加)

さて、プレーするための基本的なポンアリーナは持っていますが、まだプレイヤーとボールを追加する必要があります。ボールから始めましょう。新たに「PongBall」クラスを追加して、私たちのボールとなるwidgetを作成し、それを跳ね回るようにします。

PongBall Class(PongBallクラス)

PongBallクラスのPythonコードは以下のとおり:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class PongBall(Widget):

    # velocity of the ball on x and y axis
    velocity_x = NumericProperty(0)
    velocity_y = NumericProperty(0)

    # referencelist property so we can use ball.velocity as
    # a shorthand, just like e.g. w.pos for w.x and w.y
    velocity = ReferenceListProperty(velocity_x, velocity_y)

    # ``move`` function will move the ball one step. This
    #  will be called in equal intervals to animate the ball
    def move(self):
        self.pos = Vector(*self.velocity) + self.pos

そして、ここにボールとして白い円を描くkvルールがあります:

<PongBall>:
    size: 50, 50
    canvas:
        Ellipse:
            pos: self.pos
            size: self.size

すべての機能を有効にするには使用される Properties Propertyクラスと:class:`~kivy.vector.Vector`のインポートを追加する必要もあります。

このステップのための更新されたpythonコードとkvファイルが以下にあります:

main.py:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty
from kivy.vector import Vector


class PongBall(Widget):
    velocity_x = NumericProperty(0)
    velocity_y = NumericProperty(0)
    velocity = ReferenceListProperty(velocity_x, velocity_y)

    def move(self):
        self.pos = Vector(*self.velocity) + self.pos


class PongGame(Widget):
    pass


class PongApp(App):
    def build(self):
        return PongGame()


if __name__ == '__main__':
    PongApp().run()
pong.kv:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#:kivy 1.0.9

<PongBall>:
    size: 50, 50 
    canvas:
        Ellipse:
            pos: self.pos
            size: self.size          

<PongGame>:
    canvas:
        Rectangle:
            pos: self.center_x-5, 0
            size: 10, self.height
    
    Label:
        font_size: 70  
        center_x: root.width / 4
        top: root.top - 50
        text: "0"
        
    Label:
        font_size: 70  
        center_x: root.width * 3 / 4
        top: root.top - 50
        text: "0"
    
    PongBall:
        center: self.parent.center
        

「<PongBall>」widgetのルールの追加だけでなく、「<PongGame>」widgetルールの子widgetである「PongBall」も追加されています。

Adding Ball Animation(ボールのアニメーションを追加する)

ボールの動きを作成します

現在はボールがあり「move」関数もありますが、まだ動作してません。それを修正しよう。

Scheduling Functions on the Clock(Clockのスケジューリング関数)

ボールの「移動」方法を定期的にコールする必要があります。幸いなことにKivyでは、Clock を使用して必要な機能を間隔を指定してスケジュールすることで簡単に実行できます。

Clock.schedule_interval(game.update, 1.0/60.0)

例えば、この行は、ゲームオブジェクトの「update」機能が60分の1秒ごとに(毎秒60回)呼び出されることになります。

Object Properties/References(オブジェクトのプロパティとリファレンス(参照))

別の問題があります。 PongBallには定期的に呼び出される「move」関数があることを確認したいが、コードでは「PongGame」クラスのkvルールの中のkvファイルを使ってボールオブジェクトへの参照を追加しているので、ゲームへの唯一の参照は、アプリケーション構築メソッドで返すものです。

ボールを移動するだけではなく、「PongGame」クラスの「update」メソッドが必要になるでしょう。さらに、ゲームオブジェクトへの参照は既にあるのでアプリケーションの構築時に新しいupdateメソッドを簡単にスケジュールできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class PongGame(Widget):

    def update(self, dt):
        # call ball.move and other stuff
        pass

class PongApp(App):

    def build(self):
        game = PongGame()
        Clock.schedule_interval(game.update, 1.0/60.0)
        return game

しかし、それでもkvルールで作成され「PongBall」の子widgetへの参照がないという事実は変わりません。これを修正するために、PongGameクラスに:class:ObjectProperty<kivy.properties.ObjectProperty> を追加し、kvルールで作成されたwidgetに接続できます。それが終わったら、updateメソッド内でballプロパティを簡単に参照できて、エッジからはね返すこともできます:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class PongGame(Widget):
    ball = ObjectProperty(None)

    def update(self, dt):
        self.ball.move()

        # bounce off top and bottom
        if (self.ball.y < 0) or (self.ball.top > self.height):
            self.ball.velocity_y *= -1

        # bounce off left and right
        if (self.ball.x < 0) or (self.ball.right > self.width):
            self.ball.velocity_x *= -1

子のwidgetにidを与え、PongGameの「ball」ObjectPropertyをそのidに設定することによってkvファイルに接続することを忘れないでください:

<PongGame>:
    ball: pong_ball

    # ... (canvas and Labels)

    PongBall:
        id: pong_ball
        center: self.parent.center

注釈

この時点で、ボールが跳ね返るようにすべてが接続されます。ここまでコーディングしているなら、ボールがどこにも動いていないのが不思議に思うかもしれません。ボールの速度は、xyの両方で0に設定されます。以下のコードリストでは、「serve_ball」メソッドが「PongGame」クラスに追加され、アプリケーションの「build」メソッドで呼び出されます。ボールのxとyのランダムな速度を設定し、位置をリセットするので後でボールをリセットしてポイントを獲得することができます。

このステップのコード全体を以下に示します:

main.py:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty,\
    ObjectProperty
from kivy.vector import Vector
from kivy.clock import Clock
from random import randint


class PongBall(Widget):
    velocity_x = NumericProperty(0)
    velocity_y = NumericProperty(0)
    velocity = ReferenceListProperty(velocity_x, velocity_y)

    def move(self):
        self.pos = Vector(*self.velocity) + self.pos


class PongGame(Widget):
    ball = ObjectProperty(None)

    def serve_ball(self):
        self.ball.center = self.center
        self.ball.velocity = Vector(4, 0).rotate(randint(0, 360))

    def update(self, dt):
        self.ball.move()

        # bounce off top and bottom
        if (self.ball.y < 0) or (self.ball.top > self.height):
            self.ball.velocity_y *= -1

        # bounce off left and right
        if (self.ball.x < 0) or (self.ball.right > self.width):
            self.ball.velocity_x *= -1


class PongApp(App):
    def build(self):
        game = PongGame()
        game.serve_ball()
        Clock.schedule_interval(game.update, 1.0 / 60.0)
        return game


if __name__ == '__main__':
    PongApp().run()
pong.kv:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#:kivy 1.0.9

<PongBall>:
    size: 50, 50 
    canvas:
        Ellipse:
            pos: self.pos
            size: self.size          

<PongGame>:
    ball: pong_ball
    
    canvas:
        Rectangle:
            pos: self.center_x-5, 0
            size: 10, self.height
    
    Label:
        font_size: 70  
        center_x: root.width / 4
        top: root.top - 50
        text: "0"
        
    Label:
        font_size: 70  
        center_x: root.width * 3 / 4
        top: root.top - 50
        text: "0"
    
    PongBall:
        id: pong_ball
        center: self.parent.center
        

Connect Input Events(入力イベントの接続)

プレイヤーを追加してタッチ入力に反応する

素敵です。ボールはバウンドしています。今足りない機能は可動式のプレーヤーラケットとスコアの記録です。これらの概念はすでに前のステップでカバーされていたので、クラスとkvのルールを作成する詳細の全ては説明しません。代わりに、ユーザの入力に応じてPlayer widgetを移動する方法に焦点を当てましょう。このセクションの最後に、PongPaddleクラスの全コードとkvルールを得ることができます。

Kivyのwidgetは、on_touch_downon_touch_moveon_touch_up の各メソッドを実装することで入力に反応できます。デフォルトでは、Widgetクラスはいずれかの子が「True」を返すまでイベントを渡すため、すべての子のwidgetで対応するメソッドを呼び出すだけでこれらのメソッドを実装します。

Pongはかなりシンプルです。ラケットは上下に動かすだけです。実際とてもシンプルなので、プレーヤーwidgetがイベント自体を処理する必要はありません。 PongGameクラスの「on_touch_move」関数を実装して画面の左右にタッチが発生したかどうかから左右のプレーヤーの位置を設定します。

on_touch_move ハンドラーをチェックします:

1
2
3
4
5
def on_touch_move(self, touch):
    if touch.x < self.width/3:
        self.player1.center_y = touch.y
    if touch.x > self.width - self.width/3:
        self.player2.center_y = touch.y

NumericProperty では、各プレイヤーのスコアを保持します。 「PongGame」のスコアラベルはNumericPropertyスコアを変更することで更新され、「PongGame」の子ラベルのtext propertyが更新されます。このバインディングは、Kivyプ properties が対応するkvファイル内の任意の参照に自動的にバインドされるために発生します。ボールが両サイドから飛び出したら「PongGame」クラスの更新メソッドを変更してスコアを更新してボールを再び提供します。また、「PongPaddle」クラスは「bounce_ball」メソッドを実装しているため、ボールがラケットに当たる場所に応じてボールは違う跳ね方をします。 「PongPaddle」クラスのコードは以下のとおりです。

1
2
3
4
5
6
7
8
9
class PongPaddle(Widget):

    score = NumericProperty(0)

    def bounce_ball(self, ball):
        if self.collide_widget(ball):
            speedup  = 1.1
            offset = 0.02 * Vector(0, ball.center_y-self.center_y)
            ball.velocity =  speedup * (offset - ball.velocity)

注釈

ボールが跳ねるアルゴリズムは非常に簡単ですが、ボールがサイドまたはボトムからパドルに当たった場合におかしな動作をします...これはあなたが好きなときに自身で修正する余地があります。:

これはコンテキストにあります。大体終わりました:

main.py:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty,\
    ObjectProperty
from kivy.vector import Vector
from kivy.clock import Clock


class PongPaddle(Widget):
    score = NumericProperty(0)

    def bounce_ball(self, ball):
        if self.collide_widget(ball):
            vx, vy = ball.velocity
            offset = (ball.center_y - self.center_y) / (self.height / 2)
            bounced = Vector(-1 * vx, vy)
            vel = bounced * 1.1
            ball.velocity = vel.x, vel.y + offset


class PongBall(Widget):
    velocity_x = NumericProperty(0)
    velocity_y = NumericProperty(0)
    velocity = ReferenceListProperty(velocity_x, velocity_y)

    def move(self):
        self.pos = Vector(*self.velocity) + self.pos


class PongGame(Widget):
    ball = ObjectProperty(None)
    player1 = ObjectProperty(None)
    player2 = ObjectProperty(None)

    def serve_ball(self, vel=(4, 0)):
        self.ball.center = self.center
        self.ball.velocity = vel

    def update(self, dt):
        self.ball.move()

        # bounce of paddles
        self.player1.bounce_ball(self.ball)
        self.player2.bounce_ball(self.ball)

        # bounce ball off bottom or top
        if (self.ball.y < self.y) or (self.ball.top > self.top):
            self.ball.velocity_y *= -1

        # went of to a side to score point?
        if self.ball.x < self.x:
            self.player2.score += 1
            self.serve_ball(vel=(4, 0))
        if self.ball.x > self.width:
            self.player1.score += 1
            self.serve_ball(vel=(-4, 0))

    def on_touch_move(self, touch):
        if touch.x < self.width / 3:
            self.player1.center_y = touch.y
        if touch.x > self.width - self.width / 3:
            self.player2.center_y = touch.y


class PongApp(App):
    def build(self):
        game = PongGame()
        game.serve_ball()
        Clock.schedule_interval(game.update, 1.0 / 60.0)
        return game


if __name__ == '__main__':
    PongApp().run()

pong.kv:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#:kivy 1.0.9

<PongBall>:
    size: 50, 50 
    canvas:
        Ellipse:
            pos: self.pos
            size: self.size          

<PongPaddle>:
    size: 25, 200
    canvas:
        Rectangle:
            pos:self.pos
            size:self.size

<PongGame>:
    ball: pong_ball
    player1: player_left
    player2: player_right
    
    canvas:
        Rectangle:
            pos: self.center_x-5, 0
            size: 10, self.height
    
    Label:
        font_size: 70  
        center_x: root.width / 4
        top: root.top - 50
        text: str(root.player1.score)
        
    Label:
        font_size: 70  
        center_x: root.width * 3 / 4
        top: root.top - 50
        text: str(root.player2.score)
    
    PongBall:
        id: pong_ball
        center: self.parent.center
        
    PongPaddle:
        id: player_left
        x: root.x
        center_y: root.center_y
        
    PongPaddle:
        id: player_right
        x: root.width-self.width
        center_y: root.center_y
        

Where To Go Now?(どこに行く?)

楽しんできてね

さて、ポンゲームは大体完成しました。チュートリアルで取り上げている内容をすべて理解しているならば今度はゲームを改善する方法について考えてみてください。改善するためのいくつかのアイデアがあります:

  • よいグラフィックス/画像をいくつか追加します。 (ヒント:画像をテクスチャとして設定するには、 circleRectangle のようなグラフィックス命令の source propertyをチェックしてください。)

  • 特定のスコアの後にゲームを終了させます。プレイヤーが10ポイントを獲得したら、「PLAYER 1 WINS」ラベルを大きく表示したり、メインメニューを追加してゲームを開始、一時停止、リセットできるようにします。(ヒント: class:~kivy.uix.button.Button クラスと Label クラスをチェックアウトし、「add_widget」と「remove_widget」関数を使ってウィジェットを動的に追加または削除する方法を解説します。

  • 4人プレーのPong Gameにしてください。ほとんどのタブレットにはマルチタッチがサポートされているため両面にプレイヤーを置いて4人同時に遊ぶのはクールではないでしょうか?

  • 単純な衝突チェックを修正して、パドルの終わりでボールを打つと、より現実的な跳ねる動きが得られます。

注釈

各ステップのソースコードとファイル全体のソースコードはKivyのexamplesディレクトリのtutorials / pong / にあります。