読者です 読者をやめる 読者になる 読者になる

人工知能に関する断創録

人工知能、認知科学、心理学、ロボティクス、生物学などに興味を持っています。このブログでは人工知能のさまざまな分野について調査したことをまとめています。最近は、機械学習・Deep Learningに関する記事が多いです。



マップ間移動

Pygame

別のマップに移動できるようにします。王様たちも待っていることですし、今回はお城でも作りましょう。

pyrpg18.zip
f:id:aidiary:20100807180848p:plain f:id:aidiary:20100807180849p:plain

移動イベント

キャラクターイベント(2008/6/22)と同じように移動イベントを配置することでマップ間移動を行います。プレイヤーが移動イベントの上に乗ったら他のマップへ移動するようにします。たとえば、左のスクリーンショットにお城がありますが、これは4つの移動イベントから成っています。フィールドマップ(field)のイベントファイル(field.evt)を見てください。 下のように4つのイベントが書いてあります。

 MOVE,5,5,24,castle,7,15
 MOVE,6,5,25,castle,8,15
 MOVE,5,6,26,castle,7,15
 MOVE,6,6,27,castle,8,15

MOVEは移動イベントであることを意味してます。次の (5,5) は移動イベントの座標です。次の24はマップチップIDです。マップチップ(2008/6/24)参照。24は

25は

26は

27は

ですね。この4つをうまく配置するとお城になります。次の3つは移動先マップ名と移動先座標です。castleの (7,15) へ移動することを意味してます。移動先マップ名はマップを入れたファイル名と同じである必要があります。じゃ、移動イベントをロードするコードを見てみます。上のイベントファイルをロードしてMoveEventオブジェクトを作成してマップに登録するところです。これは、Mapのload_event()ですね。キャラクターイベント(2008/6/22)をロードしたのと同じ場所です。

    def load_event(self):
        """ファイルからイベントをロード"""
        file = os.path.join("data", self.name + ".evt")
        # テキスト形式のイベントを読み込む
        fp = codecs.open(file, "r", "utf-8")
        for line in fp:
            line = line.rstrip()  # 改行除去
            if line.startswith("#"): continue  # コメント行は無視
            data = line.split(",")
            event_type = data[0]
            if event_type == "CHARA":  # キャラクターイベント
                self.create_chara(data)
            elif event_type == "MOVE":  # 移動イベント
                self.create_move(data)
        fp.close()

    def create_move(self, data):
        """移動イベントを作成してeventsに追加する"""
        x, y = int(data[1]), int(data[2])
        mapchip = int(data[3])
        dest_map = data[4]
        dest_x, dest_y = int(data[5]), int(data[6])
        move = MoveEvent((x,y), mapchip, dest_map, (dest_x,dest_y))
        self.events.append(move)

MOVEのときcreate_move()で移動イベントを作成してます。create_move()では移動イベントの作成に必要な情報を読み取ってMoveEventオブジェクトを作成してます。作成した移動イベントはMapのeventsに登録します。eventsはイベントを格納するリストです。移動イベントや後で作る宝箱や扉イベントを格納します。キャラクターのcharasとは別に作成しましたがここら辺は後で変更するかもしれません。最後に移動イベントMoveEventクラスを見てみます。

class MoveEvent():
    """移動イベント"""
    def __init__(self, pos, mapchip, dest_map, dest_pos):
        self.x, self.y = pos[0], pos[1]  # イベント座標
        self.mapchip = mapchip  # マップチップ
        self.dest_map = dest_map  # 移動先マップ名
        self.dest_x, self.dest_y = dest_pos[0], dest_pos[1]  # 移動先座標
        self.image = Map.images[self.mapchip]
        self.rect = self.image.get_rect(topleft=(self.x*GS, self.y*GS))
    def draw(self, screen, offset):
        """オフセットを考慮してイベントを描画"""
        offsetx, offsety = offset
        px = self.rect.topleft[0]
        py = self.rect.topleft[1]
        screen.blit(self.image, (px-offsetx, py-offsety))
    def __str__(self):
        return "MOVE,%d,%d,%d,%s,%d,%d" % (self.x, self.y, self.mapchip, self.dest_map, self.dest_x, self.dest_y)

コンストラクタに渡すのは、イベントファイルに書いた内容と同じです。マップチップのイメージは、Map.imagesに入っているのでそれを使います。

マップ間移動

次にプレイヤーが移動イベントをふんだら別のマップに移動する処理を書きます。「移動イベントをふんだら」なので移動が完了したところにマップ移動処理を書けばいいですね。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: ここに接触イベントのチェックを入れる
                event = map.get_event(self.x, self.y)
                if isinstance(event, MoveEvent):  # MoveEventなら
                    dest_map = event.dest_map
                    dest_x = event.dest_x
                    dest_y = event.dest_y
                    # 移動先のマップで再構成
                    map.create(dest_map)
                    # プレイヤーを移動先座標へ
                    self.set_pos(dest_x, dest_y, DOWN)
                    # マップに再登録
                    map.add_chara(self)

ピクセル移動でプレイヤーの移動が1マス完了したところに移動処理を書いています。Mapのget_event()でイベントがあるか調べてそれが MoveEventなら移動を開始します。MoveEventから移動先マップの情報を読み込んでMapのcreate()を呼び出しています。mapは現在のマップです。現在のマップに対してcreate()すると別のマップが再構成されます。

    def create(self, dest_map):
        """dest_mapでマップを初期化"""
        self.name = dest_map
        self.charas = []
        self.events = []
        self.load()
        self.load_event()

マップをload()してイベントもload_event()し直していますね。あとは、プレイヤーを移動先座標にset_pos()して新しいマップに再登録します。これでプレイヤーは別のマップに立っています。

注1

Pythonに詳しい人はわかると思いますが、

map = Map(dest_map)

というように新しいMapオブジェクトを作ってmapに格納する方法ではうまくいきません。mapに代入してもupdate()呼び出し元のmapには何の影響もないんですね。ここはJavaとは違うのではまりました。

注2

マップを移動するときにマップ情報とイベントをファイルからロードしています。ゲーム中のすべてのマップをあらかじめメモリにロードしておく方法も考えられますが、今回はロードするたびにファイルから読み込む方法を取りました。たぶんこっちの方が一般的です。市販ゲームでもマップ切り替えのときNow Loadingって出ますよね?