【100日チャレンジ 5日目】Python と GitHub Copilot でコンソール版ブロック崩しを作ってみた!

ゲーム画面

100日チャレンジも5日目に突入!今日は少し気分を変えて、懐かしのブロック崩しゲームをコンソール(ターミナル)で動くバージョンで作ってみることにしました。

ちょうど GitHub Copilot の使い方にも慣れていきたいと思っていたので、今回は Copilot と相談しながら、まるでペアプログラミングのように、少しずつ機能を足していくスタイルで開発を進めてみました。

今日やったこと

まずはシンプルなブロック崩しを動かすことを目標に、基本的な機能を作っていきました。

  1. 開発環境の準備とプロジェクトの骨組み作り
    • Python (3.8) と Pygame ライブラリを用意しました。( pip install pygame )
    • Copilot に相談しながら、今後の拡張も見据えて、下のようなフォルダ構成にしてみました。ソースコード(src)、音声ファイル(assets/sounds)、フォントファイル(assets/fonts)などを分けて整理しています。
005Day_Breakout/
├── src/
│ ├── main.py # ゲーム起動!
│ ├── game.py # ゲームの心臓部
│ └── assets/
│ ├── sounds/ # 効果音たち
│ │ ├── bounce.wav
│ │ └── brick_break.wav
│ └── fonts/ # 文字表示用フォント
│ └── NotoSansJP-Regular.ttf
└── README.md # プロジェクトの説明書

GitHubはこちらからアクセス

https://github.com/miyakawa2449/100day/tree/main/005Day_Breakout

  1. ゲームのコア機能!
    • Copilot に基本的な動きのアイデアをもらいつつ、パドルを左右に動かしてボールを打ち返し、ブロックを壊す、というブロック崩しの基本的なロジックを実装しました。
    • ボールが下に落ちたらライフが減って、0になったらゲームオーバー。
    • 全部のブロックを壊したらゲームクリア!というルールも入れました。
  2. 画面表示をちょこっとリッチに
    • 画面の右上に残りのライフを表示するようにしました。ハラハラ感が出ますね!
    • ゲームオーバー時やクリア時に「Game Over」「You Win」といったメッセージが出るようにしました。
  3. 音でゲームを盛り上げる!
    • やっぱりゲームには音がないと!ということで、ボールがパドルやブロックに当たった時に効果音が鳴るようにしました。
    • サウンド素材は、フリー素材サイトの OtoLogic さんと 効果音ラボ さんからお借りしました。(いつもありがとうございます!)
    • ダウンロードした音声ファイルは assets/sounds/ フォルダに入れました。
  4. 日本語表示も忘れずに
    • せっかくなので、画面に日本語も表示したいと思い、「Noto Sans CJK JP」フォントを使ってみることに。画面の下部に、お借りした素材(OtoLogicさん)へのクレジット表記を追加しました。
    • フォントファイルは assets/fonts/ フォルダに配置しました。
  5. 仕上げとドキュメント
    • プロジェクト全体の構成を 005Day_Breakout というフォルダにまとめ、README.md ファイルに、どんなプロジェクトなのか、どうやって動かすのか、使わせていただいた素材などを書き込みました。

Pygameで作るブロック崩し!game.pyの注目ポイント解説

VSCode

Pygameを使ってシンプルなブロック崩しゲームを作るチュートリアル、今回はゲームのメインロジックを担当する game.py の中身を見ていきましょう。全てのコードを解説すると長くなってしまうので、今回は特に重要な3つのポイントに絞って解説します。

ポイント1:ゲームの心臓部!メインループ (run メソッド)

どんなゲームにも、ユーザーからの入力を受け付け、ゲームの状態を更新し、画面を描画する、という一連の流れを繰り返す「メインループ」があります。game.py では run メソッドがその役割を担っています。

# Python
def run(self):
        self.initialize()  # ゲームの初期化
        clock = pygame.time.Clock()  # フレームレート制御用

        while self.running: # ゲーム実行中はこのループが回り続ける
            # 1. イベント処理 (終了イベントなど)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.running = False

            # 2. 入力処理 (パドル操作)
            keys = pygame.key.get_pressed()
            if keys[pygame.K_LEFT] and self.paddle.left > 0:
                self.paddle.x -= 5
            if keys[pygame.K_RIGHT] and self.paddle.right < 800:
                self.paddle.x += 5

            # 3. ゲーム状態の更新 (ボールの移動、衝突判定など)
            self.update()
            self.check_game_over() # ゲームオーバー/クリア判定

            # 4. 描画処理 (画面のクリアとオブジェクト描画)
            self.screen.fill((0, 0, 0))  # 背景を黒で塗りつぶす
            self.draw(self.screen)      # 各要素を描画
            pygame.display.flip()       # 画面を更新

            # 5. フレームレート制御
            clock.tick(60) # 1秒間に60回ループするように調整

このループは self.runningTrue の間、以下の処理を高速で繰り返します。

  1. イベント処理: ウィンドウの閉じるボタンが押されたかなどをチェックします。
  2. 入力処理: 左右の矢印キーが押されているかを確認し、パドルを動かします。
  3. ゲーム状態の更新: update() メソッドを呼び出し、ボールの移動や衝突判定など、ゲーム内部の状態を計算します。また、check_game_over() でゲーム終了条件を満たしていないか確認します。
  4. 描画処理: screen.fill() で画面を一度真っ黒にクリアし、draw() メソッドで現在のゲーム状態(パドル、ボール、ブロック、スコアなど)を描画します。最後に pygame.display.flip() で実際に画面に表示します。
  5. フレームレート制御: clock.tick(60) で、ループの速度が一定(この場合は秒間60フレーム)になるように調整します。これにより、どのコンピュータでも同じようなゲーム速度で遊べるようになります。

この「イベント処理 → 入力処理 → 更新 → 描画」という流れは、多くのゲームに共通する基本的な構造です。

ポイント2:ゲーム性の核!衝突判定 (update メソッド内)

ブロック崩しゲームの面白さの核心は、ボールが壁やパドル、ブロックに当たって跳ね返る物理的な挙動と、ブロックを壊す爽快感にあります。これらの処理は update メソッド内で行われています。

# Python
def update(self):
        # ボールの移動
        self.ball.x += self.ball_speed[0]
        self.ball.y += self.ball_speed[1]

        # 壁との衝突判定
        if self.ball.left <= 0 or self.ball.right >= 800:
            self.ball_speed[0] = -self.ball_speed[0]  # x方向の速度を反転
        if self.ball.top <= 0:
            self.ball_speed[1] = -self.ball_speed[1]  # y方向の速度を反転
        # (下の壁との衝突判定はライフ減少処理)

        # パドルとの衝突判定
        if self.ball.colliderect(self.paddle): # colliderectで矩形同士の衝突を検出
            self.ball_speed[1] = -self.ball_speed[1]  # y方向の速度を反転
            self.bounce_sound.play()  # 効果音再生

        # ブロックとの衝突判定
        for brick in self.bricks[:]: # リストをコピーしてループ (ループ中の削除対策)
            if self.ball.colliderect(brick):
                self.bricks.remove(brick)  # 衝突したブロックをリストから削除
                self.ball_speed[1] = -self.ball_speed[1]  # y方向の速度を反転
                self.score += 10  # スコアを加算
                self.brick_break_sound.play() # 効果音再生
                break # 1フレームで複数のブロックを壊さないようにループを抜ける

ここでは Pygame の Rect オブジェクトが持つ colliderect() メソッドが大活躍します。これは、2つの矩形(この場合はボールと壁、ボールとパドル、ボールとブロック)が重なっているかどうかを判定する便利なメソッドです。

  • 壁との衝突: ボールの left, right, top 座標が画面の端に達したら、対応する方向の速度 (ball_speed[0] または ball_speed[1]) の符号を反転させて、跳ね返りを表現します。
  • パドルとの衝突: ball.colliderect(self.paddle)True になったら、ボールのy方向の速度を反転させ、効果音を鳴らします。
  • ブロックとの衝突: self.bricks リスト内の各ブロック (brick) に対して ball.colliderect(brick) をチェックします。衝突していたら、そのブロックを self.bricks.remove(brick) でリストから削除し、ボールのy方向の速度を反転、スコアを加算し、効果音を鳴らします。break でループを抜けることで、1回の衝突で複数のブロックを同時に壊してしまうのを防いでいます。また、ループ中にリストから要素を削除する際は self.bricks[:] のようにリストのコピーに対してループを回すのが安全な方法です。

このように、colliderect() を使って衝突を検出し、それに応じて速度を変えたり、オブジェクトを削除したりすることで、ゲームのインタラクションを実現しています。

ポイント3:画面にゲーム世界を描く (draw メソッド)

ゲームの状態を計算するだけでは遊べません。計算結果をプレイヤーが見えるように画面に描画する必要があります。これが draw メソッドの役割です。

# Python
def draw(self, screen):
        # パドルを描画 (白い四角形)
        pygame.draw.rect(screen, (255, 255, 255), self.paddle)

        # ボールを描画 (白い円)
        pygame.draw.ellipse(screen, (255, 255, 255), self.ball)

        # ブロックを描画 (赤い四角形)
        for brick in self.bricks:
            pygame.draw.rect(screen, (255, 0, 0), brick)

        # スコアを描画
        font = pygame.font.Font(None, 36) # フォントオブジェクト作成
        score_text = font.render(f"Score: {self.score}", True, (255, 255, 255)) # テキスト画像作成
        screen.blit(score_text, (10, 10)) # 画面に貼り付け

        # ライフを描画 (緑の四角形)
        for i in range(self.lives):
            pygame.draw.rect(screen, (0, 255, 0), (700 + i * 30, 10, 20, 20))

        # (追加情報テキストの描画 - フォントは__init__で読み込み済み)
        info_text = self.info_font.render("パドルの弾く音はOtoLogicの素材を使用している", True, (255, 255, 255))
        screen.blit(info_text, (10, 570))

raw メソッドは、引数として受け取った screen (Pygameの画面オブジェクト) に対して描画命令を発行します。

  • 図形の描画: pygame.draw.rect() で四角形(パドル、ブロック、ライフ)、pygame.draw.ellipse() で円(ボール)を描画しています。第1引数に描画先の screen、第2引数に色(RGBタプル)、第3引数に Rect オブジェクト(位置とサイズ)を指定します。
  • テキストの描画:
    1. pygame.font.Font() でフォントオブジェクトを作成します(__init__ で読み込んだカスタムフォントを使う場合は self.info_font のように既存のオブジェクトを使います)。
    2. font.render() で表示したい文字列、アンチエイリアス(True/False)、文字色を指定し、テキストが描画された Surface オブジェクト(画像のようなもの)を生成します。
    3. screen.blit() で、生成したテキスト Surface を指定した座標 ((10, 10) など) に画面上に貼り付け(転写)します。

run メソッド内で毎回 screen.fill((0, 0, 0)) によって画面がクリアされた後、この draw メソッドが呼ばれることで、常に最新のゲーム状態が画面に表示される仕組みです。

まとめ

今回は100日チャレンジの5日目として、PygameとGitHub Copilotを使いながら、懐かしのブロック崩しゲームの基本的な形を実装しました。Copilotに相談しながら進めることで、プロジェクトの構成から考え、サウンドや日本語フォントの導入など、細かな機能もスムーズに追加することができました。

特に game.py の中核となるメインループ(イベント処理→入力→更新→描画のサイクル)、衝突判定colliderect を活用したオブジェクト間のインタラクション)、そして描画処理(図形やテキストによる画面表現)は、ゲーム開発の基礎となる重要な要素です。これらの仕組みを実際にコードに落とし込むことで、ゲームが動く原理への理解が深まりました。

まだシンプルな状態ですが、ここからステージを追加したり、アイテム要素を加えたりと、さらにゲームを面白くしていく拡張の余地がたくさんあります。GitHub Copilotとのペアプログラミングも、アイデア出しや定型的なコードの記述で非常に役立つことが実感できました。

100日チャレンジはまだ始まったばかり。この調子で、楽しみながら開発スキルを向上させていきたいと思います!

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール