パーティー
今までずっとプレイヤーは1人だったので仲間を増やしてパーティを組みます。説明を読む前にスクリプトを動かしてパーティの各プレイヤーの動きがどうなっているか観察してみることをおすすめします。観察のポイントは、先頭ではないプレイヤーは1人前にいるプレイヤーがいる位置に移動する点です。どんなにくねくね移動しようがそうなってます。
パーティを組む
パーティを表すPartyクラスを作りましたが、まず使い方から見てみます。main()です。
party = Party() # パーティ # プレイヤーを複数作成 player1 = Player("swordman_female", (3,5), DOWN, True, party) player2 = Player("elf_female2", (3,4), DOWN, False, party) player3 = Player("priestess", (3,3), DOWN, False, party) player4 = Player("magician_female", (3,2), DOWN, False, party) # パーティへキャラクターを追加 party.add(player1) party.add(player2) party.add(player3) party.add(player4)
partyオブジェクトを作ったあとに4人のプレイヤーを作成してadd()でパーティへ追加しているだけですね。Playerクラスのコンストラクタに新しく2つ引数を追加しています。最後の2つです。
class Player(Character): """プレイヤークラス""" def __init__(self, name, pos, dir, leader, party): Character.__init__(self, name, pos, dir, False, None) self.leader = leader self.party = party
leaderとpartyですね。先頭にいるキャラクターのみleader=Trueとなります(なりますというかそうしてください!)。また、partyはPartyオブジェクトへの参照です。この2つがなぜ必要かというとマップ間移動などプレイヤーが上に乗ったときに発動するイベントは先頭のプレイヤー(leader)にのみ適用したいからです。Playerのupdate()を見てみると
if not self.leader: return # リーダーでなければイベントは無視
という一文があります。リーダー以外は移動イベントに乗っても移動しません。partyがなぜ必要かというとリーダーが移動イベントに乗ったときリーダーだけでなくパーティー内の全員が移動先マップに移動しないとダメだからです。同じくPlayerのupdate()に
for player in self.party.member: player.set_pos(dest_x, dest_y, DOWN) # プレイヤーを移動先座標へ player.moving = False
という文があります。partyのメンバー全員を移動先座標にセットしています。ここら辺は微妙で移動イベントは先頭プレイヤーのみ発動したいけれど毒沼はパーティ全員バシバシしたいですね。どうしようか・・・
Partyクラスの詳細
次にPartyクラスの詳細を見ていきます。
class Party: def __init__(self): # Partyのメンバーリスト self.member = [] def add(self, player): """Partyにplayerを追加""" self.member.append(player) def update(self, map): # Party全員を更新 for player in self.member: player.update(map) # 移動中でないときにキー入力があったらParty全員を移動開始 if not self.member[0].moving: pressed_keys = pygame.key.get_pressed() if pressed_keys[K_DOWN]: # 先頭キャラは移動できなくても向きは変える self.member[0].direction = DOWN # 先頭キャラが移動できれば if map.is_movable(self.member[0].x, self.member[0].y+1): # 後ろにいる仲間から1つ前の仲間の位置へ移動開始 for i in range(len(self.member)-1,0,-1): self.member[i].move_to(self.member[i-1].x,self.member[i-1].y) # 先頭キャラを最後に移動開始 self.member[0].move_to(self.member[0].x,self.member[0].y+1) elif pressed_keys[K_LEFT]: self.member[0].direction = LEFT if map.is_movable(self.member[0].x-1, self.member[0].y): for i in range(len(self.member)-1,0,-1): self.member[i].move_to(self.member[i-1].x,self.member[i-1].y) self.member[0].move_to(self.member[0].x-1,self.member[0].y) elif pressed_keys[K_RIGHT]: self.member[0].direction = RIGHT if map.is_movable(self.member[0].x+1, self.member[0].y): for i in range(len(self.member)-1,0,-1): self.member[i].move_to(self.member[i-1].x,self.member[i-1].y) self.member[0].move_to(self.member[0].x+1,self.member[0].y) elif pressed_keys[K_UP]: self.member[0].direction = UP if map.is_movable(self.member[0].x, self.member[0].y-1): for i in range(len(self.member)-1,0,-1): self.member[i].move_to(self.member[i-1].x,self.member[i-1].y) self.member[0].move_to(self.member[0].x,self.member[0].y-1) def draw(self, screen, offset): # Partyの全員を描画 # 重なったとき先頭キャラが表示されるように後ろの人から描画 for player in self.member[::-1]: player.draw(screen, offset)
まず、Partyにはパーティ内のメンバーを格納するリストmemberがあります。add()でPlayerオブジェクトを渡すとmemberに追加されます。これはさっき見ましたね。次にupdate()ですが、Partyのmember全員のupdate()を呼び出しています。次にキー入力があった場合、移動を開始しています。これは、前回までPlayerに合った処理ですがPartyに移しています。キー入力によってパーティ全員が移動するからです。下キーを押したときの移動処理を見てみます。
if pressed_keys[K_DOWN]: # 先頭キャラは移動できなくても向きは変える self.member[0].direction = DOWN # 先頭キャラが移動できれば if map.is_movable(self.member[0].x, self.member[0].y+1): # 後ろにいる仲間から1つ前の仲間の位置へ移動開始 for i in range(len(self.member)-1,0,-1): self.member[i].move_to(self.member[i-1].x,self.member[i-1].y) # 先頭キャラを最後に移動開始 self.member[0].move_to(self.member[0].x,self.member[0].y+1)
まず、先頭プレイヤー(leader)は移動できなくても方向は変えます。これは、1人のときと同じです。次に、先頭プレイヤーが移動できるなら、残りのメンバーも全部動かします。この残りのメンバーの動かし方が今回のポイントです。動かし方のポイントは、一番後ろにいるプレイヤーから順に1つ前にいるプレイヤーの座標へ移動することです。ただし、先頭にいるプレイヤーだけはキー入力した方向へ移動します。コード中のPlayer.move_to(x, y)は新しいメソッドですが、文字通り今いる座標から(x,y)へ移動するメソッドです。forループを見ると逆順になっています。つまり、4人プレイヤーのとき、iは3,2,1の順になります。i番目のメンバーはmove_toでi-1番目のメンバーがいる座標へ移動していることがわかります。つまり、
- 3番目のメンバーは2番目のメンバーがいる座標へ
- 2番目のメンバーは1番目のメンバーがいる座標へ
- 1番目のメンバーは0番目のメンバー(leader)がいる座標へ移動します。最後に0番目のメンバーは、今までどおりキー入力した座標へ移動します。
これがパーティ移動のポイントです。
前にいるメンバーから移動しても実は変わりませんでした。これは、ピクセルベース移動しているためです。move_to()を実行しても移動を開始するだけで実際にプレイヤーの座標(x,y)はすぐには変わりません。しかし、タイルベース移動のようにmove_to()ですぐにプレイヤーの座標が変わる場合は後ろから動かさないとおかしくなります。
今回説明したパーティ移動のテクニックは別のゲームでも使えます。たとえば、Nibblesという蛇が移動する有名なゲームは同じテクニックを使います。あとで紹介予定です。
Playerの移動処理の修正
次にPlayerのupdate()を見てみます。
def update(self, map): """プレイヤー状態を更新する。 mapは移動可能かの判定に必要。""" # プレイヤーの移動処理 if self.moving == True: # ピクセル移動中ならマスにきっちり収まるまで移動を続ける self.rect.move_ip(self.vx, self.vy) # マスにおさまったら移動完了 if self.rect.left % GS == 0 and self.rect.top % GS == 0: self.moving = False self.x = self.rect.left / GS self.y = self.rect.top / GS # TODO: ここに接触イベントのチェックを入れる if not self.leader: return # リーダーでなければイベントは無視 event = map.get_event(self.x, self.y) if isinstance(event, MoveEvent): # MoveEventなら sounds["step"].play() dest_map = event.dest_map dest_x = event.dest_x dest_y = event.dest_y map.create(dest_map) for player in self.party.member: # プレイヤーを移動先座標へ player.set_pos(dest_x, dest_y, DOWN) player.moving = False # キャラクターアニメーション self.frame += 1 self.image = self.images[self.name][self.direction*4+self.frame/self.animcycle%4] def move_to(self, destx, desty): """現在位置から(destx,desty)への移動を開始""" dx = destx - self.x dy = desty - self.y # 向きを変える if dx == 1: self.direction = RIGHT elif dx == -1: self.direction = LEFT elif dy == -1: self.direction = UP elif dy == 1: self.direction = DOWN # 速度をセット self.vx, self.vy = dx*self.speed, dy*self.speed # 移動開始 self.moving = True
今までキー入力をしたときに移動を開始する処理はPlayerのupdate()に書いていましたが、Partyに移したのできれいさっぱり消えています。今回追加したのはmove_to()です。これは、現在位置から引数で与えた (destx, desty) へ移動を開始する処理です。移動を開始するだけで実際の移動はupdate()でピクセル移動します。
プレイヤーの描画順序
最後にPartyの描画処理を見てみます。
def draw(self, screen, offset): # Partyの全員を描画 # 重なったとき先頭キャラが表示されるように後ろの人から描画 for player in self.member[::-1]: player.draw(screen, offset)
ここでのポイントは、後ろにいるプレイヤーから描画することです。描画は重ね描きなのでプレイヤー同士が重なったとき一番前にいるプレイヤーが表示されるようにしたいからです。member[::-1]はPythonの書き方で逆順リストです。self.member[::-1]をself.memberに変えて前にいるプレイヤーから描画するとどうなるか確かめてみてください。