参考:【#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()
関数の追加
変更点
- 初期化処理 (
mario
とkuribo
の生成) を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
を活用したカプセル化
変更点
-
vy
とstatus
を@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 を導入 |
マリオの死亡処理 | なし |
DEADING → DEAD のアニメーション追加 |
クリボーの衝突処理 | なし |
colliderect() を使い、踏みつけと接触で処理を分岐 |
クリボーのつぶれアニメ | なし |
__collapsecount で 30フレーム後に削除 |
ゲームリセット | なし |
time.sleep(2) 後に init() でリセット |
カプセル化 |
self.__vy など直接操作 |
@property を使用 |