Pygame を使ってメイズゲームを作成する

PythonBeginner
オンラインで実践に進む

This tutorial is from open-source community. Access the source code

はじめに

このプロジェクトでは、Python の Pygame ライブラリを使って迷路ゲームを作成します。このゲームでは、プレイヤーが迷路を進みながら壁を避けながら食べ物を集めます。開発プロセスを複数のステップに分割して、理解しやすく追いやすくします。

👀 プレビュー

迷路ゲームのプレビュー画面

🎯 タスク

このプロジェクトでは、以下を学びます。

  • Pygame を使ってゲーム環境を設定する方法
  • セルと壁を使って迷路を作成する方法
  • プレイヤーが集める食べ物を追加する方法
  • プレイヤーの移動と衝突検出を実装する方法
  • スコア付けやゲームオーバー条件を含むゲームロジックを処理する方法
  • プレイヤーの記録を追跡する方法
  • 時間、スコア、記録などのゲーム統計を画面に表示する方法

🏆 成果

このプロジェクトを完了すると、以下ができるようになります。

  • ゲーム開発に Pygame ライブラリを使用する
  • オブジェクト指向プログラミングの概念を適用してゲーム要素を作成する
  • 迷路生成に対するアルゴリズム思考と問題解決能力を示す
  • イベント処理とプレイヤー入力を処理する
  • ゲーム環境における衝突検出と移動メカニズムを実装する
  • ゲーム記録の保存と取得のためのファイル処理を管理する
  • 画面にゲーム統計と情報を表示する

環境の設定

まず、迷路ゲーム用のプロジェクトファイルを作成します。

cd ~/project
touch maze.py
sudo pip install pygame

このステップでは、Pygame 環境をセットアップし、定数を定義します。

import pygame
from random import choice, randrange

## 画面サイズとタイルサイズの定数
RES = WIDTH, HEIGHT = 1202, 902
TILE = 100
cols, rows = WIDTH // TILE, HEIGHT // TILE

## 残りのコードはここに書きます...

このステップでは:

  • 必要なライブラリ(Pygame と random)をインポートします。
  • 画面サイズとタイルサイズの定数を定義します。
  • Pygame を初期化し、ゲームウィンドウをセットアップします。
  • ゲーム用の背景画像を読み込みます。

セルクラスの作成

このステップでは、迷路のセルを表すCellクラスを定義します。

## 迷路のセルを表すクラスを定義
class Cell:
    def __init__(self, x, y):
        self.x, self.y = x, y
        ## 壁はセルの境界を表します
        self.walls = {"top": True, "right": True, "bottom": True, "left": True}
        self.visited = False
        self.thickness = 4

    ## セルの壁を描画する
    def draw(self, sc):
        x, y = self.x * TILE, self.y * TILE
        if self.walls["top"]:
            pygame.draw.line(
                sc, pygame.Color("darkorange"), (x, y), (x + TILE, y), self.thickness
            )
        if self.walls["right"]:
            pygame.draw.line(
                sc,
                pygame.Color("darkorange"),
                (x + TILE, y),
                (x + TILE, y + TILE),
                self.thickness,
            )
        if self.walls["bottom"]:
            pygame.draw.line(
                sc,
                pygame.Color("darkorange"),
                (x + TILE, y + TILE),
                (x, y + TILE),
                self.thickness,
            )
        if self.walls["left"]:
            pygame.draw.line(
                sc, pygame.Color("darkorange"), (x, y + TILE), (x, y), self.thickness
            )

    ## セルの各壁を表す四角形を取得する
    def get_rects(self):
        rects = []
        x, y = self.x * TILE, self.y * TILE
        if self.walls["top"]:
            rects.append(pygame.Rect((x, y), (TILE, self.thickness)))
        if self.walls["right"]:
            rects.append(pygame.Rect((x + TILE, y), (self.thickness, TILE)))
        if self.walls["bottom"]:
            rects.append(pygame.Rect((x, y + TILE), (TILE, self.thickness)))
        if self.walls["left"]:
            rects.append(pygame.Rect((x, y), (self.thickness, TILE)))
        return rects

    ## 隣接するセルが存在するかどうかを確認する
    def check_cell(self, x, y):
        find_index = lambda x, y: x + y * cols
        if x < 0 or x > cols - 1 or y < 0 or y > rows - 1:
            return False
        return self.grid_cells[find_index(x, y)]

    ## 訪問されていない隣接セルを取得する
    def check_neighbors(self, grid_cells):
        self.grid_cells = grid_cells
        neighbors = []
        top = self.check_cell(self.x, self.y - 1)
        right = self.check_cell(self.x + 1, self.y)
        bottom = self.check_cell(self.x, self.y + 1)
        left = self.check_cell(self.x - 1, self.y)
        if top and not top.visited:
            neighbors.append(top)
        if right and not right.visited:
            neighbors.append(right)
        if bottom and not bottom.visited:
            neighbors.append(bottom)
        if left and not left.visited:
            neighbors.append(left)
        return choice(neighbors) if neighbors else False

## 残りのコードはここに書きます...

このステップでは:

  • 壁を描画し、隣接セルをチェックするためのプロパティとメソッドを持つCellクラスを定義します。

壁を取り除いて迷路を生成する

このステップでは、壁を取り除き、迷路を生成する関数を作成します。

## 2 つの隣接するセル間の壁を取り除く関数
def remove_walls(current, next):
    dx = current.x - next.x
    if dx == 1:
        current.walls["left"] = False
        next.walls["right"] = False
    elif dx == -1:
        current.walls["right"] = False
        next.walls["left"] = False
    dy = current.y - next.y
    if dy == 1:
        current.walls["top"] = False
        next.walls["bottom"] = False
    elif dy == -1:
        current.walls["bottom"] = False
        next.walls["top"] = False


## 迷路を生成する関数
def generate_maze():
    grid_cells = [Cell(col, row) for row in range(rows) for col in range(cols)]
    current_cell = grid_cells[0]
    array = []
    break_count = 1

    while break_count!= len(grid_cells):
        current_cell.visited = True
        next_cell = current_cell.check_neighbors(grid_cells)
        if next_cell:
            next_cell.visited = True
            break_count += 1
            array.append(current_cell)
            remove_walls(current_cell, next_cell)
            current_cell = next_cell
        elif array:
            current_cell = array.pop()
    return grid_cells

## 残りのコードはここに書きます...

このステップでは:

  • 隣接するセル間の壁を取り除くremove_walls関数を定義します。
  • 深さ優先探索アルゴリズムを使って迷路を生成するgenerate_maze関数を作成します。

ゲームに食べ物を追加する

このステップでは、ゲームに食べ物を追加するためのFoodクラスを作成します。

## ゲーム内の食べ物を表すクラス
class Food:
    def __init__(self):
        ## 食べ物の画像を読み込む
        self.img = pygame.image.load("img/food.png").convert_alpha()
        self.img = pygame.transform.scale(self.img, (TILE - 10, TILE - 10))
        self.rect = self.img.get_rect()
        self.set_pos()

    ## 食べ物の位置をランダムに設定する
    def set_pos(self):
        self.rect.topleft = randrange(cols) * TILE + 5, randrange(rows) * TILE + 5

    ## 食べ物を画面に描画する
    def draw(self):
        game_surface.blit(self.img, self.rect)

## 残りのコードはここに書きます...

このステップでは:

  • 食べ物の位置を設定し、描画するメソッドを持つFoodクラスを定義します。

プレイヤーの移動と衝突検出

このステップでは、プレイヤーの制御、移動、および衝突検出を設定します。

## プレイヤーが壁と衝突しているかどうかを確認する
def is_collide(x, y):
    tmp_rect = player_rect.move(x, y)
    if tmp_rect.collidelist(walls_collide_list) == -1:
        return False
    return True

## 残りのコードはここに書きます...

このステップでは:

  • プレイヤーが壁と衝突しているかどうかを確認するis_collide関数を定義します。

ゲームプレイと得点計算

このステップでは、食べ物を食べることや得点計算などのゲームプレイロジックを実装します。

## プレイヤーが食べ物を食べたかどうかを確認する
def eat_food():
    for food in food_list:
        if player_rect.collidepoint(food.rect.center):
            food.set_pos()
            return True
    return False


## ゲームが終了したかどうかを確認する(時間切れ)
def is_game_over():
    global time, score, record, FPS
    if time < 0:
        pygame.time.wait(700)
        player_rect.center = TILE // 2, TILE // 2
        [food.set_pos() for food in food_list]
        set_record(record, score)
        record = get_record()
        time, score, FPS = 60, 0, 60

## 残りのコードはここに書きます...

このステップでは:

  • プレイヤーが食べ物を食べたかどうかを確認するeat_food関数を定義します。
  • 時間切れのときにゲームが終了したかどうかを確認するis_game_over関数を作成します。

記録の処理

このステップでは、ゲームの記録管理を実装します。

## ファイルから現在の記録を取得する関数
def get_record():
    try:
        with open("record") as f:
            return f.readline()
    except FileNotFoundError:
        with open("record", "w") as f:
            f.write("0")
            return "0"

## ファイルに記録を設定して更新する関数
def set_record(record, score):
    rec = max(int(record), score)
    with open("record", "w") as f:
        f.write(str(rec))

## 残りのコードはここに書きます...

このステップでは:

  • ファイルから現在の記録を取得し、更新する関数を定義します。

ゲームの初期化

このステップでは、ゲームの初期化タスクを実行します。

## Pygame を初期化し、ゲームウィンドウを設定する
FPS = 60
pygame.init()
game_surface = pygame.Surface(RES)
surface = pygame.display.set_mode((WIDTH + 300, HEIGHT))
clock = pygame.time.Clock()

## 背景画像を読み込む
bg_game = pygame.image.load("img/bg_1.jpg").convert()
bg = pygame.image.load("img/bg_main.jpg").convert()

## 迷路を生成する
maze = generate_maze()

## プレイヤー設定
player_speed = 5
player_img = pygame.image.load("img/0.png").convert_alpha()
player_img = pygame.transform.scale(
    player_img, (TILE - 2 * maze[0].thickness, TILE - 2 * maze[0].thickness)
)
player_rect = player_img.get_rect()
player_rect.center = TILE // 2, TILE // 2
directions = {
    "a": (-player_speed, 0),
    "d": (player_speed, 0),
    "w": (0, -player_speed),
    "s": (0, player_speed),
}
keys = {"a": pygame.K_LEFT, "d": pygame.K_RIGHT, "w": pygame.K_UP, "s": pygame.K_DOWN}
direction = (0, 0)

## 食べ物設定
food_list = [Food() for i in range(3)]

## 衝突検出用の壁の矩形のリストを作成する
walls_collide_list = sum([cell.get_rects() for cell in maze], [])

## タイマー、得点、記録
pygame.time.set_timer(pygame.USEREVENT, 1000)
time = 60
score = 0
record = get_record()

## フォント
font = pygame.font.SysFont("Impact", 150)
text_font = pygame.font.SysFont("Impact", 80)

## 残りのコードはここに書きます...

このステップでは:

  • Pygame の設定、画像の読み込み、迷路の生成、プレイヤーと食べ物に関連する変数の初期化など、様々な初期化タスクを実行します。

メインゲームループ

このステップでは、メインゲームループを設定し、ゲーム要素を表示します。

## メインゲームループ
while True:
    ## 背景画像を描画する
    surface.blit(bg, (WIDTH, 0))
    surface.blit(game_surface, (0, 0))
    game_surface.blit(bg_game, (0, 0))

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit()
        if event.type == pygame.USEREVENT:
            time -= 1

    ## プレイヤーの制御と移動を処理する
    pressed_key = pygame.key.get_pressed()
    for key, key_value in keys.items():
        if pressed_key[key_value] and not is_collide(*directions[key]):
            direction = directions[key]
            break
    if not is_collide(*direction):
        player_rect.move_ip(direction)

    ## 迷路を描画する
    [cell.draw(game_surface) for cell in maze]

    ## ゲームプレイ:プレイヤーが食べ物を食べたかどうかとゲームが終了したかどうかを確認する
    if eat_food():
        FPS += 10
        score += 1
    is_game_over()

    ## プレイヤーを描画する
    game_surface.blit(player_img, player_rect)

    ## 食べ物を描画する
    [food.draw() for food in food_list]

    ## 残りのコードはここに書きます...

このステップでは:

  • イベント、プレイヤーの移動、ゲームの描画を処理するメインゲームループを設定します。

ゲーム統計情報の表示

このステップでは、画面上にゲームの統計情報を表示します。

    ## ゲーム統計情報を描画する
    surface.blit(
        text_font.render("TIME", True, pygame.Color("cyan"), True), (WIDTH + 70, 30)
    )
    surface.blit(font.render(f"{time}", True, pygame.Color("cyan")), (WIDTH + 70, 130))
    surface.blit(
        text_font.render("score:", True, pygame.Color("forestgreen"), True),
        (WIDTH + 50, 350),
    )
    surface.blit(
        font.render(f"{score}", True, pygame.Color("forestgreen")), (WIDTH + 70, 430)
    )
    surface.blit(
        text_font.render("record:", True, pygame.Color("magenta"), True),
        (WIDTH + 30, 620),
    )
    surface.blit(
        font.render(f"{record}", True, pygame.Color("magenta")), (WIDTH + 70, 700)
    )

    pygame.display.flip()
    clock.tick(FPS)

このステップでは:

  • 時間、得点、記録などのゲーム関連情報を表示するためにフォントを使用します。
  • blit() メソッドを使って画面上にテキストを描画します。
  • flip() メソッドを使って表示を更新します。

ゲームを実行する

これですべてのステップを完了したので、以下のコマンドを使ってメイズゲームを実行できます。

cd ~/project
python maze.py

メイズゲームの実行画面キャプチャ

まとめ

このプロジェクトでは、Pygame を使ってメイズゲームを作成するプロセスを、10 の明確で管理しやすいステップに分割しました。ゲーム環境の設定方法、メイズセルの作成方法、メイズの生成方法、プレイヤーの移動と衝突検出の処理方法、ゲームプレイと得点計算の実装方法、記録の管理方法などを学びます。これらのステップに従うことで、Python で完全に機能するメイズゲームを作成することができます。

✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習