pygameにはspriteという便利な機能があり、師匠のサイトにあるインベーダーのエイリアンとミサイルのように、移動しているキャラクタどうしの衝突を簡単に判定することができます。最初はspriteを使おうかと思いましたが、倉庫番ゲームの場合は動きが単純なので単純な方法で判定することにしました。その方法も師匠のサイトのRPGを参考にしています。
先ずプレーヤと壁との衝突を判定するメソッドを作りましょう。プレーヤーの座標と壁の座標を比較することになります。壁の座標はMapクラスのblocks[]に格納されていますが、プレーヤーの座標はPlayerクラスでの移動に応じて変化するので、このメソッドはPlayerクラスに作ります。プレーヤーの行き先に壁があるかないかを検知し、壁がなけれれば移動可能とします。プレーヤーの移動方向は4方向ですから、方向毎にメソッドを作成することにしました。DOWN方向のメソッドは以下のようになります。
def canMoveDown(self, map): if (self.x, self.y+1) in map.blocks: # 行先座標が壁 return False else: return True
壁座標を格納した配列blocksはMapクラスのインスタンス変数ですから、上のメソッドの引数にはMapクラスのインスタンス「map」を加えています。この「map」はsouko.pyの32行目でmap = Map()とインスタンス化しています。上のメソッドをplayers.pyに追加し、players.pyのmove()メソッドを以下のように書き換えます。souko.pyのイベント処理でmove()メソッドを使っている箇所も以下のように変更しましょう。
players.py def move(self, dir, map ): if dir == DOWN:if self.canMoveDown(map): self.dir = DOWN self.y += 1 elif dir == LEFT: self.dir = LEFT self.x -= 1 elif dir == RIGHT: self.dir = RIGHT self.x += 1 elif dir == UP: self.dir = UP self.y -= 1 ******************************* souko.py # プレイヤーの移動処理 if event.type == KEYDOWN and event.key == K_DOWN: player.move(DOWN, map ) if event.type == KEYDOWN and event.key == K_LEFT: player.move(LEFT, map ) if event.type == KEYDOWN and event.key == K_RIGHT: player.move(RIGHT, map ) if event.type == KEYDOWN and event.key == K_UP: player.move(UP, map )
soukoフォルダをカレントディレクトリとし、souko.pyを起動して好きな面を選択してプレーヤーを移動させてみて下さい。下方向に壁がある場合だけすり抜けられないですね。他の三方向はすり抜けてしまいます。DOWN方向のメソッドしか作っていないので当然です。あとはこのメソッドを4方向分作るだけです。コピペして修正するだけですから簡単です。players.pyの修正した部分だけ以下に示します。
def move(self, dir, map): if dir == DOWN: if self.canMoveDown(map): self.dir = DOWN self.y += 1 elif dir == LEFT: if self.canMoveLeft(map): self.dir = LEFT self.x -= 1 elif dir == RIGHT: if self.canMoveRight(map): self.dir = RIGHT self.x += 1 elif dir == UP: if self.canMoveUp(map): self.dir = UP self.y -= 1 def canMoveDown(self, map): if (self.x, self.y+1) in map.blocks: # 行先座標が壁 return False else: return True def canMoveUp(self, map): if (self.x, self.y-1) in map.blocks: # 行先座標が壁 return False else: return True def canMoveRight(self, map): if (self.x+1, self.y) in map.blocks: # 行先座標が壁 return False else: return True def canMoveLeft(self, map): if (self.x-1, self.y) in map.blocks: # 行先座標が壁 return False else: return True
この時点でsouko.pyを起動し、全方向で壁にぶつかることを確認してくださいね。次は荷物との衝突判定に移りましょう。荷物との衝突は壁に比べると少し複雑です。荷物の押す方向にの先に壁があると押せません。荷物の先に荷物があっても押せません。つまり押す方向にある荷物の先が空の場合にのみ押すことができ、プレーヤーと荷物が移動します。したがって例えばcanMoveDown(self, map)は以下のようになるでしょう。
001: def canMoveDown(self, map): 002: if (self.x, self.y+1) in map.blocks: # 行先座標が壁 003: return False 004: elif (self.x, self.y+1) in map.bags: # 行先座標が荷物 005: if (self.x, self.y+2) in map.blocks or (self.x, self.y+2) in map.bags: # その先座標が壁 or 荷物 006: return False 007: else: 008: map.bags.remove((self.x, self.y+1)) # 行先座標をbagsから削除 009: if (self.x, self.y+1) in map.stored: # 行先座標が格納listにあれば 010: map.stored.remove((self.x, self.y+1)) # 行先座標を格納listから削除 011: map.bags.append((self.x, self.y+2)) # その先座標をbagsに追加 012: if (self.x, self.y+2) in map.places: # その先座標が収納場所listにあれば 013: map.stored.append((self.x, self.y+2)) # その先座標を格納listに追加 014: return True 015: else: 016: return True
進方向の行き先の座標を「行先座標」、さらにその先の座標を「その先座標」といいます。2-3行は先に説明した「行先座標」が壁の場合です。4-14行は「行先座標」に荷物がある場合で、5行目で「その先座標」を調べて荷物又は壁ならFalseを返します。
「その先座標」が空の場合は、8行目でbags[]から「行先座標」を削除し、9-10行で「行先座標」がstored[]にあればstored[]からも削除して、11行目で「その先座標」をbags[]に追加しています。つまり「行先座標」にあった荷物が「その先座標」に移動します。12-13行では、「その先座標」が収納場所なら「その先座標」をstored[]に追加し、その荷物が収納されていることを規定しています。
15-16行は「行先座標」が壁でも荷物でもない場合、つまり「行先座標」が空か収納場所「.」の場合でプレーヤの移動が可能ですからTrueを返します。あとは上のメソッドを4方向分作るだけです。コピペして修正するだけですから簡単です。これで壁及び荷物との衝突を検知でき、動かせる場合にだけ荷物を動かすことができるようになりました。
ここまでで倉庫番ゲームを一応遊ぶことができますが、もっと機能を付け加えたくなります。例えば荷物を動かす方向を間違えた場合、今はやりなおしできませんから、また最初からということになります。そんな時は一歩戻りたいと思うでしょう。次は「一歩戻る」を実装します。これが実装できれば、何歩でも戻ることができますね。