0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Python】自作マリオ 敵を倒す処理と当たり判定

Posted at

参考:【#6 敵を踏み潰す】クリボーを踏み潰してみよう!そしてマリオも死ぬよ!マリオを一緒に作ってみませんか!pythonで!【ハンズオン実践解説】

1. マリオとクリボーの状態管理を Enum で統一

変更点

  • Status クラスを Enum で定義。
  • NORMAL, DEADING, DEAD の 3 つの状態を追加。
from enum import Enum, auto
import time

class Status(Enum):
    NORMAL = auto()
    DEADING = auto()
    DEAD = auto()

解説

  • Enum を導入することで、マリオやクリボーの状態管理がわかりやすくなった。
  • DEADING (死亡アニメーション中) → DEAD (完全に死亡) という状態遷移を明確化。

2. マリオの死亡アニメーションの追加

変更点

  • self.__status = Status.NORMAL を追加。
  • __deading() メソッドを追加し、死亡アニメーションを実装。
        # マリオの状態
        self.__status = Status.NORMAL
    def __deading(self):
        if self.__animecounter == 0:
            self.__vy = -12
        
        if self.__animecounter > 10:
            self.__vy += 1
            self.rect.y += self.__vy
        
        if self.rect.y > H + 20:
            self.__status = Status.DEAD
            return

        self.__animecounter += 1

解説

  • マリオが DEADING 状態になると、上にジャンプ (vy = -12) した後、徐々に落下する処理を実装。
  • 落下しきる (rect.y > H + 20) と Status.DEAD に遷移し、ゲームオーバー処理に移行。

3. クリボーとの衝突判定

変更点

  • if self.rect.colliderect(self.__mario.rect): により、マリオとクリボーの接触を検出。
  • if self.__mario.vy > 0: で踏みつけた場合 (DEADING 状態に変更)、そうでない場合はマリオが DEADING に遷移。
        if self.rect.colliderect(self.__mario.rect):
            # 当たった時の処理をする
            if self.__mario.vy > 0:
                # 踏みつぶす
                self.__status = Status.DEADING
                self.__mario.vy = -5
            else:
                self.__mario.status = Status.DEADING

解説

  • 踏みつける (vy > 0) とクリボーは DEADING になり、30フレーム後 DEAD に遷移。
  • 逆に横や下から衝突すると、マリオが DEADING に。

4. クリボーのつぶれアニメーション

変更点

  • self.__collapsecount = 0 を追加し、踏みつけ後のアニメーション用カウンタを導入。
  • self.image = self.__imgs[1] でつぶれた画像を表示。
        # つぶれたカウンタ
        self.__collapsecount = 0
def update(self):
        if self.__mario.status == Status.DEADING:
            return
        if self.__status == Status.DEADING:
            self.image = self.__imgs[1]
            self.__collapsecount += 1
            if self.__collapsecount == 30:
                self.__status = Status.DEAD
            return
        if self.__status == Status.DEAD:
            return

解説

  • 30フレーム (collapsecount == 30) 経過後に DEAD へ遷移し、group.remove(kuribo) で消滅。

5. init() 関数の追加

変更点

  • 初期化処理 (mariokuribo の生成) を init() に分離。
def init():
    # スプライトグループを定義
    group = pygame.sprite.RenderUpdates()
    # マリオクラスを定義
    mario = Mario()
    # マリオをグループに追加
    group.add(mario)
    # クリボークラスを構築
    kuribos = [
        Kuribo(200, 180, mario),
        Kuribo(260, 180, mario),
        Kuribo(300, 180, mario)
    ]
    # クリボーをグループに追加
    for kuribo in kuribos:
        group.add(kuribo)
    
    return group, mario, kuribos

        # マリオが死んだかのチェック
        if mario.status == Status.DEAD:
            time.sleep(2)
            group, mario, kuribos = init()
            continue

解説

  • mario.status == Status.DEAD になると time.sleep(2) して init() を呼び出し、ゲームをリセット。
  • main() 内のコードが整理され、可読性が向上。

6. @property を活用したカプセル化

変更点

  • vystatus@property で管理。
    @property
    def vy(self):
        return self.__vy

    @vy.setter
    def vy(self, value):
        self.__vy = value

    @property
    def status(self):
        return self.__status

    @status.setter
    def status(self, value):
        self.__status = value

解説

  • mario.vy への直接アクセスを防ぎ、統一的な setter/getter を提供。
  • オブジェクト指向的な設計に改良。

完全なコード

import pygame
from enum import Enum, auto
import time

class Status(Enum):
    NORMAL = auto()
    DEADING = auto()
    DEAD = auto()

# 画面サイズを定義
W, H = 320, 270

# タイル数
TILE_X = 16
TILE_Y = 14

class Mario(pygame.sprite.Sprite):
    ''' マリオのクラス
    '''
    WALK_ANIME_IDX = [0, 0, 0, 1, 1, 1]
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)

        # 左右どちら向きかのフラグを定義
        self.__isleft = False
        # 歩くインデックス
        self.__walkidx = 0
        # Y軸方向移動郷里
        self.__vy = 0
        # マリオが地面にいるか
        self.__on_ground = True
        # マリオの状態
        self.__status = Status.NORMAL
        # アニメ用カウンタ
        self.__animecounter = 0

        # marioの画像を読み込む
        self.__imgs = [
            pygame.image.load('mario001.png'),
            pygame.image.load('mario002.png'),
            pygame.image.load('mario003.png')
        ]
        self.image = self.__imgs[0]
        self.rect = pygame.Rect(150, 180, 20, 20)

    @property
    def vy(self):
        return self.__vy

    @vy.setter
    def vy(self, value):
        self.__vy = value

    @property
    def status(self):
        return self.__status

    @status.setter
    def status(self, value):
        self.__status = value

    def __right(self):
        self.rect.x += 5
        self.__isleft = False
        self.__walkidx += 1

    def __left(self):
        self.rect.x -= 5
        self.__isleft = True
        self.__walkidx += 1
    
    def __jump(self):
        if self.__on_ground:
            self.__vy = -10
            self.__on_ground = False

    def __deading(self):
        if self.__animecounter == 0:
            self.__vy = -12
        
        if self.__animecounter > 10:
            self.__vy += 1
            self.rect.y += self.__vy
        
        if self.rect.y > H + 20:
            self.__status = Status.DEAD
            return

        self.__animecounter += 1

    def update(self):
        if self.__status == Status.DEAD:
            return

        if self.__status == Status.DEADING:
            self.image = self.__imgs[2]
            self.__deading()
            return

        # キーボードの状態を取得
        keys = pygame.key.get_pressed()
        if keys[pygame.K_RIGHT]:
            self.__right()
        if keys[pygame.K_LEFT]:
            self.__left()
        if keys[pygame.K_SPACE]:
            self.__jump()

        # Y軸方向に移動
        if not self.__on_ground:
            self.rect.y += self.__vy
            self.__vy += 1

            if self.rect.y >= 180:
                self.rect.y = 180
                self.__on_ground = True
                self.__vy = 0

        self.image = pygame.transform.flip(self.__imgs[self.WALK_ANIME_IDX[self.__walkidx % 6]], self.__isleft, False)

class Kuribo(pygame.sprite.Sprite):
    WALK_SPEED = 10
    def __init__(self, x, y, mario):
        pygame.sprite.Sprite.__init__(self)

        # クリボーのX軸方向移動距離
        self.__dir = -2
        self.__walkidx = 0
        self.__imgs = [
            pygame.image.load('kuribo001.png'),
            pygame.image.load('kuribo002.png')
        ]
        self.__mario = mario
        self.__status = Status.NORMAL
        # つぶれたカウンタ
        self.__collapsecount = 0

        self.image = self.__imgs[0]
        self.rect = pygame.Rect(x, y, 20, 20)
    
    @property
    def status(self):
        return self.__status

    def update(self):
        if self.__mario.status == Status.DEADING:
            return
        if self.__status == Status.DEADING:
            self.image = self.__imgs[1]
            self.__collapsecount += 1
            if self.__collapsecount == 30:
                self.__status = Status.DEAD
            return
        if self.__status == Status.DEAD:
            return

        self.rect.x += self.__dir
        if self.rect.x <= 0 or self.rect.x >= 320 - self.rect.width:
            self.__dir *= -1

        self.__walkidx += 1
        if self.__walkidx == self.WALK_SPEED:
            self.__walkidx = 0
        self.image = pygame.transform.flip(self.__imgs[0], self.__walkidx < self.WALK_SPEED // 2, False)

        if self.rect.colliderect(self.__mario.rect):
            # 当たった時の処理をする
            if self.__mario.vy > 0:
                # 踏みつぶす
                self.__status = Status.DEADING
                self.__mario.vy = -5
            else:
                self.__mario.status = Status.DEADING

def init():
    # スプライトグループを定義
    group = pygame.sprite.RenderUpdates()
    # マリオクラスを定義
    mario = Mario()
    # マリオをグループに追加
    group.add(mario)
    # クリボークラスを構築
    kuribos = [
        Kuribo(200, 180, mario),
        Kuribo(260, 180, mario),
        Kuribo(300, 180, mario)
    ]
    # クリボーをグループに追加
    for kuribo in kuribos:
        group.add(kuribo)
    
    return group, mario, kuribos


def main():
    '''メイン関数'''
    # pygame初期化
    pygame.init()
    # 画面を構築
    win = pygame.display.set_mode((W, H))
    pygame.display.set_caption("Pygame Window")  # ウィンドウタイトルを設定
    # クロックを生成
    clock = pygame.time.Clock()

    group, mario, kuribos = init()

    # 背景色の初期値(黒)
    bg_color = (0, 0, 0)

    # イベントループ
    running = True
    while running:
        for e in pygame.event.get():
            if e.type == pygame.QUIT:
                running = False  # ループを抜ける

        # 背景を塗りつぶす
        win.fill((135, 206, 235))

        # グループを更新
        group.update()
        
        # マリオが死んだかのチェック
        if mario.status == Status.DEAD:
            time.sleep(2)
            group, mario, kuribos = init()
            continue

        # 死んでる敵をグループから削除
        for kuribo in kuribos:
            if kuribo.status == Status.DEAD:
                group.remove(kuribo)

        # グループを描画
        group.draw(win)

        # 画面の更新
        pygame.display.flip()

        clock.tick(30)  # FPS制限 (60FPS)

    # pygameを終了する
    pygame.quit()

# Pythonのファイルが直接実行された場合のみmain()を実行
if __name__ == '__main__':
    main()

まとめ

変更点 修正前 修正後
状態管理 bool で管理 (__on_ground など) Status Enum を導入
マリオの死亡処理 なし DEADINGDEAD のアニメーション追加
クリボーの衝突処理 なし colliderect() を使い、踏みつけと接触で処理を分岐
クリボーのつぶれアニメ なし __collapsecount で 30フレーム後に削除
ゲームリセット なし time.sleep(2) 後に init() でリセット
カプセル化 self.__vy など直接操作 @property を使用

Pygame Window 2025-02-03 17-57-07.gif

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?