ここでは、今までの内容を踏まえて、迷路を探索するゲームを作成しましょう。 プレイヤを操作し、ゴールまで導きます。 迷路は、例題12-1で用いたものを利用します。 ゴールの場所はランダムに割り当てられます。 敵も出現しますが、プレイヤは敵を倒す手段はなく、逃げなければいけません。
このようなゲームを作成しましょう。 先のページの 例題12-1 で作成したプログラムをベースにして 各種機能を追加していきます。
まずは、ゴールを設定しましょう。
以下の maze0.zip は先ほどの例題(例題12-1)を完成させたものです。
これをベースにして、以下の maze1 に取り組んでもらっても構いません。
maze1. ゴールを設定する #
ゴールは以下の画像のうち、一番上の3つの画像を用いることにします。
この画像も同じく Steper を用いてアニメーションさせるようにしましょう。
ゴールを表す Goal クラスを player.py に追加します。
class Goal:
def __init__(self, img_file, position):
self.img = pg.image.load(img_file)
self.rect = pg.Rect(position[0], position[1], 32, 32)
self.ix = 0
self.GW, self.GH = self.img.get_rect().width / MAX_IX, self.img.get_rect().height / MAX_IY
self.steper = Steper(ANIMATION_STEPS)
def blit(self, parent):
if self.steper.is_step():
self.ix = (self.ix + 1) % MAX_IX
parent.blit(self.img, self.rect, (self.ix * self.GW, 0, self.GW, self.GH))
移動しないので、Character クラスは利用しません。
ゴールの場所は、迷路全体の中からランダムに選びます。
プレイヤの初期位置は (0, 0) に固定しますので、ゴールの位置は (0, 0) 以外の場所に設定するようにします。
ex12-2.py を元にして、maze.py を以下のように作成します。
これまではプレイヤの画像をコマンドライン引数から受け取っていましたが、今回から固定します。
import pygame as pg # PyGame をインポートし、pg という名前で使えるようにする。
import sys # sys モジュールをインポートする(終了処理で使う)。
import player as p # 別の player.py に定義されている Player 型のインポートする。
import blocks
# 1. ゲームの準備をする。
pg.init()
screen = pg.display.set_mode((800, 600))
clock = pg.time.Clock()
player = p.Player("assets/woman_warrior.png")
goal = p.Goal("assets/crystal.png", blocks.random_location(screen.get_rect(), [player.rect]))
blocks = blocks.generate(30, screen.get_rect(), [player.rect])
blocks_rects = [b.rect for b in blocks] # 障害物の矩形リスト
# 2. メインループ
while True:
# 2-1 画面を初期化する。
screen.fill(pg.Color("WHITE")) # 画面を白色で塗りつぶす。
# 2-3. 絵を描いたり、判定したりする。
player.blit(screen, blocks_rects)
goal.blit(screen)
for b in blocks:
b.blit(screen)
# 2-4. 画面を表示する。
pg.display.update()
clock.tick(60) # 画面の更新を1秒間に60回以下のスピードに設定する。
# 2-5. 閉じるボタンが押されたら終了する。
for event in pg.event.get(): # イベント一覧を取得して、各イベントを調べる。
if event.type == pg.QUIT: # もし、閉じるボタンが押されたら。
pg.quit() # PyGame を終了する。これだけではウィンドウは閉じない。
sys.exit() # ウィンドウを閉じて、プログラムを終了する。
ゴールに到達したら終了する #
プレイヤを操作してゴールに接触すればクリアするようにします。
そのためにmaze.py のメインループの中で、プレイヤとゴールが接触したかどうかを調べます。
接触したかどうかは、Rectクラスのcolliderectメソッドを用いて調べます。
if player.rect.colliderect(goal.rect):
print("Goal!")
pg.quit()
sys.exit()
これでゴールに到達すればゲームが終了します。 ただ、やはりゲームですからゲームクリア画面が欲しいところです。 上記の処理の代わりに以下のようにしてみましょう。
if player.rect.colliderect(goal.rect):
font = pg.font.SysFont(None, 100)
text = font.render("GAME CLEAR!", True, pg.Color("RED"))
text_rect = text.get_rect(center=screen.get_rect().center)
screen.fill(pg.Color("WHITE"))
screen.blit(text, text_rect)
pg.display.update()
pg.time.wait(2000) # 2秒待つ
pg.quit()
sys.exit()
ブロックの画像を変更する #
blocks.py に定義されている Block クラスを以下のように修正します。
画像を固定して、assets/wall.png を用いるようにします。
ただし、この画像は縦に4つの画像が連なっているキャラチップ形式になっています。
ここでは最上部の画像を用いていますが、ランダムに画像を選ぶようにしても良いでしょう。
class Block:
def __init__(self, x, y):
self.img = pg.image.load("assets/wall.png")
self.iy = 0 # この値を 0 〜3 にランダムに変更しても良い。
self.rect = pg.Rect(x, y, 32, 32)
def blit(self, parent):
parent.blit(self.img, self.rect, (0, self.iy * 32, 32, 32))
def collide(self, other):
return self.rect.colliderect(other)
以下の maze1.zip はこれまでの内容を踏まえたものです。
これをベースにして、次の maze2 に取り組んでもらっても構いません。
maze2. ステージを導入する #
参考書1 p.154(Chapter 3.1)先の例では、ゲームクリア画面が表示されますが、その後、強制的にゲームが終了してしまいます。
そこで、クリアしたら何らかのキー操作、もしくはマウス操作でもう一度初めからゲームが開始されるようにします。
そのために、ゲームの状態を表す Stage クラスを定義します。
class Stage:
def blit(self, parent):
pass
class Playing(Stage):
def blit(self, parent):
pass
class GameClear(Stage):
def blit(self, parent):
pass
これらのクラスを stages.py に定義しましょう。
Playing クラスは、ゲームプレイ中の状態を表し、GameClear クラスは、ゲームクリア画面の状態を表します。
Stage クラスは、これらの基底クラス(親クラス、スーパークラスなど言語により言い方は異なる)となります。
Playing クラスの blit メソッドには、プレイヤやゴール、障害物を描画する処理を記述します。
GameClear クラスの blit メソッドには、ゲームクリア画面を描画する処理を記述します。
Playingオブジェクトでのが終了すると、GameClearオブジェクトに切り替わり、
GameClearオブジェクトでの処理が終了すると、再びPlayingオブジェクトに切り替わるようにします。
各クラスのコンストラクタでは、必要なオブジェクトの初期化を行います。
blitメソッドでは、画面の描画と状態の遷移を行います。
Stageの切り替えが必要であれば、切り替え先のオブジェクトを生成して返し、そうでなければ self を返すようにします。
継承 #
参考書2 p.54(Chapter 2.3) 参考書2 p.161(Chapter 5.2)
PlayingクラスとGameClearクラスは、共にStageクラスを継承してます。
class Playing(Stage):と記述することで、PlayingクラスはStageクラスを継承していることを示します。
継承すると、子オブジェクト(ここではPlayingやGameClear)は親オブジェクト(ここではStage)として扱えるようになります。
つまり、親オブジェクト型の変数に対して、子オブジェクトが代入でき、親オブジェクトのメソッドを呼び出せるようになります。
しかし、実際に呼び出されるのは子オブジェクトで定義されたメソッドです。
この性質をポリモルフィズム(Polymorphism; 多態性)と呼びます。
例えば、Stageクラスの変数stageに対して、PlayingオブジェクトやGameClearオブジェクトを代入できます。
また、stage.blit(screen)と呼び出すと、実際に呼び出されるのはPlayingオブジェクトやGameClearオブジェクトで定義されたblitメソッドです。
子クラスで親クラスのメソッドを上書きすることをオーバーライド(Override)と呼びます。
どのようにするとオーバーライドされるかは、以下のサンプルコード(inheritance.py)をご覧ください。
class Base:
def method1(self): # 親クラスで定義された method1。
print("Base#method1")
class Child1(Base):
def method1(self): # method1 をオーバーライドしている。
print("Child1#method1")
class Child2(Base):
pass # method1 を定義していない。
class Child3(Base):
def method1(self): # method1 をオーバーライドしている。
print("Child3#method1, ", end="")
super().method1() # 親クラス(Base)オブジェクトを取得し、method1 を呼び出している。
b = Base()
c1 = Child1()
c2 = Child2()
c3 = Child3()
b.method1() # Baseクラスで定義されたメソッドが呼び出される。
c1.method1() # Child1 クラスで定義されたメソッドが呼び出される。
c2.method1() # Child2 クラスでは method1 が定義されていないので、
# 親クラスである Base の method1 が呼び出される。
c3.method1() # Child3 クラスで定義された method1 が呼び出される。
ステージ管理を導入する #
maze.py を修正する
#
maze.py を以下のように修正します。
ゲーム内のメインの処理は stages.py に移動します。
maze.pyでは、stages モジュールをインポートし、Playing オブジェクトを生成して、
あとはPlayingオブジェクトのblitメソッドを呼び出すだけにします。
ゲームのメインの処理は Playing クラスの blit メソッドに移動させます。
import pygame as pg # PyGame をインポートし、pg という名前で使えるようにする。
import sys # sys モジュールをインポートする(終了処理で使う)。
import stages # stages でゲームのメインの処理を行う。
# 1. ゲームの準備をする。
pg.init()
screen = pg.display.set_mode((800, 640))
clock = pg.time.Clock()
stage = stages.Playing(screen)
# 2. メインループ
while True:
# 2-1 画面を初期化する。
screen.fill(pg.Color("WHITE")) # 画面を白色で塗りつぶす。
# 2-3. 絵を描いたり、判定したりする。
stage = stage.blit(screen, clock)
# 2-4. 画面を表示する。
pg.display.update()
clock.tick(60) # 画面の更新を1秒間に60回以下のスピードに設定する。
# 2-5. 閉じるボタンが押されたら終了する。
for event in pg.event.get(): # イベント一覧を取得して、各イベントを調べる。
if event.type == pg.QUIT: # もし、閉じるボタンが押されたら。
pg.quit() # PyGame を終了する。これだけではウィンドウは閉じない。
sys.exit() # ウィンドウを閉じて、プログラムを終了する。
stages.py を作成する
#
stages.py を以下のように作成します。
stages.py では、Stage クラス、Playing クラス、GameClear クラスを定義します。
Playingクラスと、GameClearクラスは、共にStageクラスを継承しています。
両クラスのblitメソッドでは、画面の描画と状態の遷移を行います。
Playingクラスではゲームのプレイ中の処理を行い、GameClearクラスではゲームクリア画面の処理を行います。
import blocks
import pygame as pg
import player as p
class Stage:
def blit(self, screen):
pass
class Playing(Stage):
def __init__(self, screen):
self.player = p.Player("assets/woman_warrior.png")
self.goal = p.Goal("assets/crystal.png", blocks.random_location(screen.get_rect(), [self.player.rect]))
self.blocks = blocks.generate(30, screen.get_rect(), [self.player.rect])
self.blocks_rects = [b.rect for b in self.blocks] # 障害物の矩形リスト
def blit(self, screen, clock):
self.player.blit(screen, self.blocks_rects)
self.goal.blit(screen)
for b in self.blocks:
b.blit(screen)
if self.player.rect.colliderect(self.goal.rect):
return GameClear(screen)
return self
class GameClear(Stage):
def __init__(self, screen):
self.steps = 0 # 経過フレーム数を保存する。
self.surf = pg.Surface((screen.get_width(), screen.get_height())) # キャンバスを作成する
self.surf.fill(pg.Color("WHITE")) # 白色で塗りつぶす。
font = pg.font.SysFont(None, 100)
text = font.render("GAME CLEAR!", True, pg.Color("BLUE"))
text_rect = text.get_rect(center = self.surf.get_rect().center) # text を親キャンバスの中央に配置する。
self.surf.blit(text, text_rect)
def blit(self, screen, clock):
self.steps = self.steps + 1
keys = pg.key.get_pressed()
mouses = pg.mouse.get_pressed()
screen.blit(self.surf, (0, 0))
# 60フレームの間は操作があっても何もしない(1秒はクリア画面を表示する)。
# その後キー操作、マウス操作が何かあれば、Playing ステージに戻る。
if self.steps > 60 and (any(keys) or any(mouses)):
return Playing(screen)
return self
以下の maze2.zip はこれまでの内容を踏まえたものです。
これをベースにして、次の maze3 に取り組んでもらっても構いません。
maze3. 敵キャラクタを導入する #
Enemy クラスを player.py に追加します。
このクラスのオブジェクト、すなわち、敵キャラクタは自由に動き回ります。
そこでまずは、動き回るためのクラス Walker を player.py に作成しましょう。
Walkerクラスを作成する
#
Walkerクラスは walk メソッドを持ち、引数として親オブジェクト、障害物の矩形リスト、
移動させる敵キャラクタの座標を表す Rect オブジェクトを受け取ります。
返り値には、移動後のキャラクタの座標を表す Rect オブジェクトと、移動方向を表す定数を返します。
MOVE_UP = 0
MOVE_DOWN = 1
MOVE_LEFT = 2
MOVE_RIGHT = 3
NO_MOVE = 4
class Walker:
def walk(self, parent, blocks, enemy_rect):
return enemy_rect, NO_MOVE
Walkerクラスのサブクラスを作成する
#
Walkerクラスを継承させて、以下の3つのクラスを作成しましょう。
- ランダムに動き回る
RandomWalkerクラス。 - 縦方向に往復移動する
VerticalWalkerクラス。 - 横方向に往復移動する
HorizontalWalkerクラス。
また、移動したい先に移動できるかどうかを判定する関数 _can_move も作成します。
RandomWalkerクラスを作成する
#
RandomWalkerクラスをWalkerクラスを継承させて作成しましょう。
RandomWalkerはwalkメソッドを持ちます。
walkメソッドでは、上下左右のいずれかに一歩だけ移動し、移動先のRectオブジェクトと移動方向を表す定数を返します。
引数として、親キャンバス、障害物の矩形リスト、移動させるキャラクタの座標を表すRectオブジェクトを受け取ります。
一方、_can_move関数は、移動先に移動できるかどうかを判定します。
親キャンバス、障害物の矩形リスト、移動させるキャラクタの座標、
移動方向を受け取り、移動できるならTrue、移動できないならFalse を返します。
def _can_move(parent, blocks, rect, dxy):
nbox = rect.move(dxy[0], dxy[1]) # 移動先の Rect を取得する。
# 既存のブロックと衝突していない かつ 親キャンバスの内側であれば移動できる。
if nbox.collidelist(blocks) == -1 and parent.contains(nbox):
return True
return False # 移動できない。
class RandomWalker(Walker):
def walk(self, parent, blocks, rect):
steps = [(1, 0), (0, 1), (-1, 0), (0, -1)] # 上下左右のいずれかに一歩だけ移動する。
index = int(random.random() * len(steps)) # steps の中のいずれか一つをランダムに選択する。
if _can_move(parent, blocks, rect, steps[index]): # 移動できるなら移動する。
s = steps[index]
dir = NO_MOVE
if s == (1, 0):
dir = MOVE_LEFT # 左に移動する。
elif s == (0, 1):
dir = MOVE_DOWN # 下に移動する。
elif s == (0, -1):
dir = MOVE_UP # 上に移動する。
else:
dir = MOVE_RIGHT # 右に移動する。
return rect.move(steps[index]), dir # 移動先の Rect と移動方向を返す。
return rect, NO_MOVE # 移動できない場合はそのまま。
VerticalWalkerクラスを作成する
#
続けて、VerticalWalkerクラスをWalkerクラスを継承させて作成しましょう。
このクラスは上下方向に動き、障害物に当たれば反対方向に移動する動きを表します。
このクラスもwalkメソッドを持ちます。
class VerticalWalker(Walker):
def __init__(self):
self.dy = -1 # 最初は上方向に移動する。
def walk(self, parent, blocks, rect):
if not _can_move(parent, blocks, rect, (0, self.dy)):
self.dy = self.dy * -1 # 移動できなければ反対方向に移動する。
direction = NO_MOVE
if self.dy < 0:
direction = MOVE_UP
else:
direction = MOVE_DOWN
return rect.move((0, self.dy)), direction
Horizontalalkerクラスを作成する
#
最後に HorizontalWalker クラスを Walker クラスを継承させて作成しましょう。
このクラスは左右方向に動き、障害物に当たれば反対方向に移動する動きを表します。
このクラスの定義は移動方向は異なるものの、VerticalWalkerクラスとほぼ同じです。
VerticalWalkerクラスを参考にして作成しましょう。
class HorizontalWalker(Walker):
# VerticalWalker と同じように作成しましょう。
Enemyクラスを作成する
#
いよいよ、Enemyクラスをplayer.pyに作成しましょう。
Enemyクラスは、Playerクラスと同じく、内部にCharacterオブジェクトを持ちます。
Playerクラスを参考に作成していきましょう。
class Enemy:
# Playerのコンストラクタで渡されるものに加えて、walker も受け取るようにする。
def __init__(self, img_file, pos, walker = None):
self.character = Character(img_file)
self.rect = pg.Rect(pos[0], pos[1], 32, 32)
# walker が指定されなければ RandomWalker を使う。
if walker is None:
self.walker = RandomWalker()
else:
self.walker = walker
def blit(self, parent, blocks):
# 移動先の矩形と移動方向を walker オブジェクトから取得する。
self.rect, direction = self.walker.walk(parent.get_rect(), blocks, self.rect)
# 敵キャラクタの向きを設定する。
if direction == MOVE_UP:
self.character.turn_up()
elif direction == MOVE_DOWN:
self.character.turn_down()
elif direction == MOVE_LEFT:
self.character.turn_left()
else:
self.character.turn_right()
# self.character.turn(direction)
self.character.blit(parent, self.rect)
Character型に turn(direction) メソッドを追加して、Enemyクラスから呼び出すようにしても良いでしょう。
複数の敵キャラクタをまとめるクラスEnemiesを作成する
#
複数の敵キャラクタををまとめるクラスEnemiesをplayer.pyに作成しましょう。
このクラスは、複数のEnemyオブジェクトを内部に持ちます。
このクラスを定義しない場合、maze.pyのメインループで複数の敵キャラクタを管理する必要があります。
Enemiesクラスを作成することで、複数の敵キャラクタの管理を一つのオブジェクトにまとめることができます。
class Enemies:
def __init__(self, enemies): # 敵キャラクタを受け取り、保持する。
self.enemies = enemies
def rect(self): # 敵キャラクタのリストから、各敵キャラクタの矩形を取り出し、リストで返す。
return [e.rect for e in self.enemies]
def collide(self, player): # プレイヤと敵キャラクタのいずれかが衝突しているかを判定する。
return player.rect.collidelist(self.rect()) > 0
def blit(self, parent, blocks): # 各敵キャラクタを描画する。
for e in self.enemies:
e.blit(parent, blocks)
ステージに敵キャラクタを配置する #
stages.py の Playing クラスのコンストラクタを修正して、敵キャラクタを配置しましょう。
generate_enemies 関数を作成して、複数の敵キャラクタを生成します。
また、playerモジュール(player.py)はpとしてインポートしているので、
p.RandomWalker()のようにしてRandomWalkerクラスなどを利用します。
def _generate_enemy(pos):
images = ["assets/ghost1.png", "assets/goblin.png", "assets/skelton.png"]
value = random.randint(0, 2)
if value == 0:
return p.Enemy(images[value], pos, p.RandomWalker())
elif value == 1:
return p.Enemy(images[value], pos, p.VerticalWalker())
else:
return p.Enemy(images[value], pos, p.HorizontalWalker())
def generate_enemies(number, parent, assigned_object):
result = []
assigned = assigned_object
while len(result) < number:
pos = blocks.random_location(parent.get_rect(), assigned) # 空き場所を取得する。
enemy = _generate_enemy(pos)
result.append(enemy) # 結果のリストに追加する。
assigned.append(enemy.rect) # すでに配置されたキャラクタの上には配置しないようにする。
return p.Enemies(result)
class Playing(Stage):
def __init__(self, screen):
self.player = # プレイヤを初期化する。
self.goal = # ゴールを初期化する。
self.enemies = generate_enemies(15, screen, [self.player.rect])
assigned = self.enemies.rect() + [self.player.rect, self.goal.rect]
self.blocks = blocks.generate(30, screen.get_rect(), assigned)
self.blocks_rects = [b.rect for b in self.blocks] # 障害物の矩形リスト
def blit(self, screen, clock):
self.player.blit(screen, self.blocks_rects)
self.enemies.blit(screen, self.blocks_rects)
self.goal.blit(screen)
for b in self.blocks:
b.blit(screen)
if self.enemies.collide(self.player):
return GameClear(screen)
if self.player.rect.colliderect(self.goal.rect):
return GameClear(screen)
return self
ゲームオーバー画面を導入する #
先ほど書いたPlayingのblitメソッドでは、プレイヤと敵キャラクタが衝突した場合にもGameClearオブジェクトに遷移していました。
これをGameOverオブジェクトに遷移するように修正します。
GameOverクラスはGameClearクラスとほぼ同じですので、stages.pyに以下のように追加します。
class GameOver(Stage):
def __init__(self, screen):
self.surf = pg.Surface((screen.get_width(), screen.get_height()), pg.SRCALPHA)
self.surf.fill(pg.Color("WHITE"))
font = pg.font.SysFont(None, 100)
text = font.render("GAME OVER!", True, pg.Color("RED"))
text_rect = text.get_rect(center = self.surf.get_rect().center)
self.steps = 0
self.surf.blit(text, text_rect)
def blit(self, screen, clock):
self.steps = self.steps + 1
keys = pg.key.get_pressed()
mouses = pg.mouse.get_pressed()
screen.blit(self.surf, (0, 0))
# 60フレームの間は操作があっても何もしない(1秒はクリア画面を表示する)。
# その後キー操作、マウス操作が何かあれば、Playing ステージに戻る。
if self.steps > 60 and (any(keys) or any(mouses)):
return Playing(screen)
return self
以下の maze3.zip はこれまでの内容を踏まえたものです。
これをベースにして、次の maze4 に取り組んでもらっても構いません。
maze4. 経過時間を計測する #
Timer クラスを導入する
#
コンストラクタにて、開始時刻を保存し、elapsed メソッドで経過時間を秒単位で取得できるようにします。
また、str関数で文字列化できるよう__str__メソッドも定義します。
文字列化では、HH:MM:SS.SSS形式でフォーマットした文字列を返すようにします。
そして、blitメソッドで画像化した文字列をキャンバスの右下に表示します。
import time
class Timer:
def __init__(self):
# 開始時刻のUNIX時間を取得する。
# https://docs.python.org/ja/3/library/time.html#time.time
self.start = time.time()
self.font = # システムフォント(18ポイント)を取得する。
def elapsed(self):
return # 現在時刻(time.time())から開始時刻(self.start)を引いた値を返す。
def __str__(self):
elapsed = self.elapsed()
minutes, seconds = divmod(elapsed, 60)
hours, minutes = divmod(minutes, 60)
return f"{int(hours):02}:{int(minutes):02}:{seconds:06.3f}"
def blit(self, parent):
text_img = # このオブジェクトの文字列表現を画像化する。
rect = text_img.get_rect()
rect.bottomright = parent.get_rect().bottomright
parent.blit(text_img, rect)
クリア画面にクリア時間を表示する #
クリア画面を表示するGameClearクラスのコンストラクタにて、Timerオブジェクトを受け取るようにします。
そして、blitメソッドでクリア時間を表示するようにします。
class GameClear(Stage):
def __init__(self, screen, elapsed):
self.steps = 0
self.surf = pg.Surface((screen.get_width(), screen.get_height()), pg.SRCALPHA)
self.surf.fill(pg.Color("WHITE"))
font = pg.font.SysFont(None, 100)
text = font.render("GAME CLEAR!", True, pg.Color("BLUE"))
text_rect = text.get_rect(center = self.surf.get_rect().center)
self.surf.blit(text, text_rect)
time_font = pg.font.SysFont(None, 25)
clear_time = time_font.render(f"Clear time: {str(elapsed)}", True, pg.Color("BLACK"))
ct_rect = clear_time.get_rect(center = self.surf.get_rect().center) # X方向を中央にする。
ct_rect.y = (text_rect.y + text_rect.height + 10) # クリア時間のy方向を Game Clear のテキストの下にする。
self.surf.blit(clear_time, ct_rect)
def blit(self, screen, clock):
self.steps = self.steps + 1
keys = pg.key.get_pressed()
mouses = pg.mouse.get_pressed()
screen.blit(self.surf, (0, 0))
# 60フレームの間は操作があっても何もしない(1秒はクリア画面を表示する)。
# その後キー操作、マウス操作が何かあれば、Playing ステージに戻る。
if self.steps > 60 and (any(keys) or any(mouses)):
return Playing(screen)
return self
上記の変更に加えて、Playing クラスも変更が必要です。
コンストラクタにて Timer オブジェクトを生成し、blitメソッドで、GameClearオブジェクトを生成する際に、
Timerオブジェクトを渡すようにします。
blitメソッドの最後に、経過時間を画面に表示するようにします。
class Playing(Stage):
def __init__(self, screen):
# ... 省略
# 一番最後に以下の行を追加する。
self.elapsed = Timer()
def blit(self, screen, clock):
# ... 省略
# GameClear オブジェクトを生成する際に、self.timer を渡すようにする。
return GameClear(screen, self.elapsed)
# ... 省略
# return の前で経過時間を描画する。
self.elapsed.blit(screen) # 画面に経過時間を表示する。
return self
以下の maze4.zip はこれまでの内容を踏まえたものです。
プログラムの全体像を把握するための参考にしてください。