人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

ボールの反射

次に画面内を跳ね回るボールを実装します。パドルにボールを当てると跳ね返ります。

breakout02.zip
f:id:aidiary:20100807231134p:plain

ボールスプライト

まずボールを表すクラスです。これもパドルと同じくSpriteを継承してデフォルトスプライトグループ(containers)も指定します。

class Ball(pygame.sprite.Sprite):
    """ボール"""
    speed = 5
    def __init__(self, paddle):
        pygame.sprite.Sprite.__init__(self, self.containers)
        self.image, self.rect = load_image("ball.png")
        self.dx = self.dy = 0  # ボールの速度
        self.paddle = paddle  # パドルへの参照
        self.update = self.start
    def start(self):
        """ボールの位置を初期化"""
        # パドルの中央に配置
        self.rect.centerx = self.paddle.rect.centerx
        self.rect.bottom = self.paddle.rect.top
        # 左クリックで移動開始
        if pygame.mouse.get_pressed()[0] == 1:
            self.dx = self.speed
            self.dy = -self.speed
            # update()をmove()に置き換え
            self.update = self.move
    def move(self):
        """ボールの移動"""
        self.rect.centerx += self.dx
        self.rect.centery += self.dy
        # 壁との反射
        if self.rect.left < SCR_RECT.left:  # 左側
            self.rect.left = SCR_RECT.left
            self.dx = -self.dx  # 速度を反転
        if self.rect.right > SCR_RECT.right:  # 右側
            self.rect.right = SCR_RECT.right
            self.dx = -self.dx
        if self.rect.top < SCR_RECT.top:  # 上側
            self.rect.top = SCR_RECT.top
            self.dy = -self.dy
        # パドルとの反射
        if self.rect.colliderect(self.paddle.rect) and self.dy > 0:
            self.dy = -self.dy
        # ボールを落とした場合
        if self.rect.top > SCR_RECT.bottom:
            self.update = self.start  # ボールを初期状態に

main()でスプライトグループに対してupdate()が呼ばれるとそのグループに含まれるスプライトも更新されます。そのためパドルスプライトはupdate()を実装していました。all.update()が呼び出されるとPaddle.update()が呼び出されてパドルが移動するというしかけです。しかし、Ballクラスを見てもupdate()がありません。代わりに

 self.update = self.start
 self.update = self.move

のようなコードがあります。これは、self.updateに関数を代入するコードです。関数は呼び出すときはself.start()というように()をつけますが、関数自体はself.startで表します。実際、

 print self.start

などとしてみると値が代入されていることがわかります。self.updateにself.startが格納されている状態で self.update()を呼び出すと代わりにself.start()が呼び出されます。また、self.updateにself.moveが格納されている状態でself.update()を呼び出すと代わりにself.move()が呼び出されます。コンストラクタでは、self.startを代入しています。つまり、最初の状態でupdate()を呼び出すとself.start()が呼ばれます。self.start()はボールをパドルの中央に配置したまま待機し、マウスクリックするとボールを発射させる処理です。スクリプトを動かすと最初この状態です。ボールを発射した後は、self.moveを代入してボールを移動させます。self.move()では、ボールを移動してます。また壁やパドルに当たると反射します。ボールの移動や反射のやり方は画像の移動と跳ね返り処理(2008/5/9)に書きました。

後はパドルとの反射ですが、Rect.colliderect()という関数を使っています。この関数は、A.colliderect(B)という形で使います。AとBはRectオブジェクト(矩形範囲)です。もし、AとBの矩形範囲が衝突していたらTrueを返します。上の例では、AにはBall.rect、BにはPaddle.rectを与えています。つまり、ボールとパドルが衝突したらTrueになります。衝突したらボールの速度を反転して上に行くようにしてますね。self.dy > 0を追加したのはボールが下向きに移動しているときに衝突判定したかったからです。上向きに移動しているときに衝突したと判定されてしまうこともまれにあるのでそれを防いでいます。最後にボールを落としたときには、self.updateをself.startにセットし直し、ボールをパドルの中央に戻しています。またマウスクリックするとボールが発射されます。

ボールの更新と描画

ボールの更新と描画はパドルと同じくmain()です。

def main():
    pygame.init()
    screen = pygame.display.set_mode(SCR_RECT.size)
    pygame.display.set_caption(u"Breakout 02 ボールの反射")
    
    # スプライトグループを作成して登録
    all = pygame.sprite.RenderUpdates()
    Paddle.containers = all
    Ball.containers = all
    
    # パドルを作成するとスプライトグループallに自動的に追加される
    paddle = Paddle()
    # ボールを作成するとスプライトグループallに自動的に追加される
    Ball(paddle)
    
    clock = pygame.time.Clock()
    while True:
        clock.tick(60)
        screen.fill((0,0,0))
        all.update()
        all.draw(screen)
        pygame.display.update()
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            if event.type == KEYDOWN and event.key == K_ESCAPE:
                pygame.quit()
                sys.exit()

ボールのデフォルトスプライトグループもパドルと同じくallにしています。all.update()とall.draw()でパドルとスプライトの両方が更新、描画されます。先ほど説明したようにボールのupdate()は代わりにstart()かmove()が呼ばれます。これでボールが反射できるようになりました。まだブロックがないのでブロック崩しになっていませんが、これはこれでテニスやエアホッケーみたいなゲーム(?)なってますね。向こう側に対戦相手がいて2人でプレイできると別のゲームになるかも。