ボールの反射
次に画面内を跳ね回るボールを実装します。パドルにボールを当てると跳ね返ります。
ボールスプライト
まずボールを表すクラスです。これもパドルと同じく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人でプレイできると別のゲームになるかも。