見出し画像

【Python入門】PyGameでブロック崩しゲームを作る

地元茅ヶ崎のボランティア活動の一環で開催しているPython入門講座のネタ帳第2弾「ブロック崩しゲーム」です.
第1弾の「Pongゲーム」はミニマルな機能に徹して、PyGameを使ったゲームプログラムの骨格構造に慣れることと、Pythonの基本文法(if文やwhile文など)を少しずつ学ぶことを目的にしました.
今回のプロジェクトでは、「リスト」と「for文」を使うことで、登場するオブジェクト(今回はブロックですね)がたくさんあっても、短いコードで効率よく実現できる方法を学びます.

前提環境

Python 3, PyGameを使います

Pongゲームのコードを母体に「縦型のPongゲーム」を作る

Pongゲームのコードは前回の記事に貼り付けてあります.

最初のステップとして、Pongゲームのコードを母体に「縦型のPongゲーム」を作ります.改造ポイントは次のような感じです.

  1. 画面の縦横比を縦長にする

  2. パドルを画面の下に配置し、左右の矢印キーで動くようにする

  3. 左右の壁と天井でボールが跳ね返るようにする

  4. ボールがパドルにあたった場合の跳ね返りをY軸方向にする

  5. 色をそれっぽくする

改造した「縦型のPongゲーム」のコードです.

import sys
import pygame as pg

WIDTH, HEIGHT = 600, 800
FPS = 30
pg.init()
screen = pg.display.set_mode((WIDTH, HEIGHT))
clock = pg.time.Clock()
# パドルとボールの初期化
paddle = pg.Rect(WIDTH // 2, HEIGHT - 30, 50, 10)
ball = pg.Rect(WIDTH // 2, HEIGHT // 2, 5, 5)
vx, vy = 4, 6

while True:
    for e in pg.event.get():
        if e.type == pg.QUIT:
            pg.quit()
            sys.exit()
    # パドルの位置を更新
    keys = pg.key.get_pressed()
    if keys[pg.K_LEFT]:
        paddle.move_ip(-10, 0)
    if keys[pg.K_RIGHT]:
        paddle.move_ip(10, 0)
    # ボールの位置を更新
    if ball.left  <= 0 or WIDTH <= ball.right:
        vx = -vx
    if ball.top <= 0:
        vy = -vy
    if HEIGHT <= ball.bottom:
        pg.time.wait(1000)
        ball = pg.Rect(WIDTH // 2, HEIGHT // 2, 5, 5)
        vx, vy = 4, 6
    ball.move_ip(vx, vy)
    # パドルとボールの衝突判定
    if paddle.colliderect(ball):
        vy = -vy
    # ゲーム画面の更新
    screen.fill('black')
    pg.draw.rect(screen, 'yellow', paddle)
    pg.draw.rect(screen, 'white', ball)
    pg.display.update()
    clock.tick(FPS)

ブロックを効率よく管理する

今回は6段10列の合計60個のブロックを登場させます.これらのブロックの状態として次のようなものを管理する必要があります.

  1. ブロック(長方形オブジェクトpg.Rectを使う)の位置と大きさ

  2. ブロックがまだ生き残っているか、すでに消えたか

  3. ブロックの色

  4. ブロックを消したときのポイント

ここで、1と2はブロックの数分、3と4はブロックの段数分の情報が必要になることに注目しましょう.

ブロックを管理するリストを作る

ブロックの段数をROWS、列数をCOLSという定数で定義して左上端のブロックから順番に位置を決めていきます.

ROWS, COLS = 6, 10
block_width = WIDTH // COLS
blocks = []
for r in range(ROWS):
    for c in range(COLS):
        rect = pg.Rect(c * block_width, 80 + r * 30, block_width-1, 25)
        blocks.append(rect)
is_alive = [True] * len(blocks)

これで60個のブロックの位置と大きさがblocksという名前のリストに順番に格納されました.またis_aliveという名前のリストで、各ブロックが残っているかどうかの状態を管理します.例えば:

blocks[0]

で左上端のブロックに対応する長方形オブジェクトが参照できます.また,

is_alive[0]

で左上端のブロックが生き残っているかどうかを参照できます.

ブロックの段に対応した色とポイントを定義する

ブロックを描画する際の色情報、ブロックが消されたときに加算するポイントをリストで定義しておきます.

COLORS = ['red', 'orange', 'yellow', 'green', 'purple', 'blue']
POINTS = [10, 9, 7, 5, 3, 1]

例えばblocksリストの先頭からi番目のブロックはi // COLS段目にあると計算できるので次のように参照できます.

color = COLORS[i // COLS]
point = POINTS[i // COLS]

ブロックを表示する

blocksリストにブロックの位置とサイズ、is_aliveリストにそのブロックが生き残っているかどうかの状態、COLORSリストに色情報が入っているので、それらの情報を使って画面に表示します.メインループ中で次のようにすることで生き残っているブロックだけが、しかるべき色で表示されます.

    for i in range(len(blocks)):
        if is_alive[i]:
            rect = blocks[i]
            color = COLORS[i // COLS]
            pg.draw.rect(screen, color, rect)

ブロックにボールがあたった時の処理

blocksリストの中の各ブロックについて次の処理をします.

  1. まだ生き残っているブロックにボールがあたったら:

  2. ブロックの状態を「消えたブロック」に変更する

  3. スコアにしかるべきポイントを加算する

  4. ボールを跳ね返す

  5. すべてのブロックが消えたことがわかったらゲーム終了処理へ

    for i in range(len(blocks)):
        block = blocks[i]
        if is_alive[i] and ball.colliderect(block):
            is_alive[i] = False
            score += POINTS[i//COLS]
            vy = -vy
            if sum(is_alive) == 0:
                game_over = True
            break

まとめ

ここまでの追加処理で最初のコードを改造すると、一応プレイできるブロック崩しができます.コード全体は下のようになります.

import sys
import pygame as pg

WIDTH, HEIGHT = 600, 800
FPS = 30
pg.init()
screen = pg.display.set_mode((WIDTH, HEIGHT))
clock = pg.time.Clock()
# パドルとボールの初期化
paddle = pg.Rect(WIDTH // 2, HEIGHT - 30, 50, 10)
ball = pg.Rect(WIDTH // 2, HEIGHT // 2, 5, 5)
vx, vy = 4, 6

ROWS, COLS = 6, 10
block_width = WIDTH // COLS
blocks = []
for r in range(ROWS):
    for c in range(COLS):
        rect = pg.Rect(c * block_width, 80 + r * 30, block_width-1, 25)
        blocks.append(rect)
is_alive = [True] * len(blocks)
COLORS = ['red', 'orange', 'yellow', 'green', 'purple', 'blue']
POINTS = [10, 9, 7, 5, 3, 1]

score = 0
while True:
    for e in pg.event.get():
        if e.type == pg.QUIT:
            pg.quit()
            sys.exit()
    # パドルの位置を更新
    keys = pg.key.get_pressed()
    if keys[pg.K_LEFT]:
        paddle.move_ip(-10, 0)
    if keys[pg.K_RIGHT]:
        paddle.move_ip(10, 0)
    # ボールの位置を更新
    if ball.left  <= 0 or WIDTH <= ball.right:
        vx = -vx
    if ball.top <= 0:
        vy = -vy
    if HEIGHT <= ball.bottom:
        pg.time.wait(1000)
        ball = pg.Rect(WIDTH // 2, HEIGHT // 2, 5, 5)
        vx, vy = 4, 6
    ball.move_ip(vx, vy)
    # パドルとボールの衝突判定
    if vy > 0 and paddle.colliderect(ball):
        vy = -vy
    # ブロックとボールの衝突判定
    for i in range(len(blocks)):
        block = blocks[i]
        if is_alive[i] and ball.colliderect(block):
            is_alive[i] = False
            score += POINTS[i//COLS]
            vy = -vy
            if sum(is_alive) == 0:
                game_over = True
            break

    # ゲーム画面の更新
    screen.fill('black')
    for i in range(len(blocks)):
        if is_alive[i]:
            rect = blocks[i]
            color = COLORS[i // COLS]
            pg.draw.rect(screen, color, rect)
    pg.draw.rect(screen, 'yellow', paddle)
    pg.draw.rect(screen, 'white', ball)
    pg.display.update()
    clock.tick(FPS)

ゲームとしての完成度を上げるために

一応プレイできるゲームができたところで、ゲームとしての完成度を上げる改造を受講生のみなさんにやってもらいます.主な改造ポイントは:

  1. 効果音を鳴らす

  2. パドルとボールの衝突ポイントで反射角度を変える

  3. ボールの初速と角度にランダムさを加える

  4. スコアと残りプレーヤー数を表示する

  5. 残りプレーヤー数が0になったらゲームオーバーにする


この記事が気に入ったらサポートをしてみませんか?