人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

宝箱と扉を開ける

今回は3つのイベントを追加します。宝箱と扉と一般オブジェクトです。宝箱と扉はスクリーンショットで見えますね。扉は前に立ってスペースキーを押すと開きます。宝箱は上に乗ってスペースキーを押すと調べます。一般オブジェクトとは玉座・いす・机などの特にアクション(開くとか)はないけどマップ上にある物体を表すイベントです。よーく見ると王様がちゃっかり玉座に座ってますね(笑)この3つのイベントは、キャラクターイベント(2008/6/22)と同じようにイベントファイルに書きます。

pyrpg22.zip
f:id:aidiary:20100807182612p:plain

イベントファイルとロード

まず、お城のイベントファイルを見てみます。

BGM,castle
CHARA,king,7,2,0,0,そなたにほうびをつかわす。
CHARA,minister,6,3,0,0,ほうもつこの たからばこは/じゆうにあけてよいぞ。
CHARA,soldier,6,13,2,0,スペースキーで とびらがあきます。/たからばこも スペースキーで/あけられます。
TREASURE,2,6,100ゴールド
TREASURE,3,6,はがねのつるぎ
TREASURE,4,6,やくそう
DOOR,3,8
OBJECT,7,2,44
OBJECT,8,2,44
MOVE,7,15,3,field,5,6
MOVE,8,15,3,field,6,6

BGMとCHARAとMOVEは前にやりました。今回は、宝箱イベントTREASUREと扉イベントDOORと一般オブジェクトイベントOBJECTが追加されています。TREASUREは、宝箱の座標とアイテム名が引数です。DOORは扉の座標が引数です。一般オブジェクトは、座標とマップチップIDが引数です。宝箱と扉はマップチップが決まっているので指定する必要がありませんが、一般オブジェクトは机やいすになったりするので指定します。44は玉座です。このイベントファイルをロードするのはMapのload_event()です。

    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 == "BGM":  # BGMイベント
                self.play_bgm(data)
            elif event_type == "CHARA":  # キャラクターイベント
                self.create_chara(data)
            elif event_type == "MOVE":  # 移動イベント
                self.create_move(data)
            elif event_type == "TREASURE":  # 宝箱
                self.create_treasure(data)
            elif event_type == "DOOR":  # とびら
                self.create_door(data)
            elif event_type == "OBJECT":  # 一般オブジェクト(玉座など)
                self.create_obj(data)
        fp.close()

TREASURE、DOOR、OBJECTイベントのとき、対応するオブジェクトを作成するメソッドを呼び出しています。3つともほとんど同じなのでここではcreate_treasure()だけ見てみます。

    def create_treasure(self, data):
        """宝箱を作成してeventsに追加する"""
        x, y = int(data[1]), int(data[2])
        item = data[3]
        treasure = Treasure((x,y), item)
        self.events.append(treasure)

引数のdataから座標とアイテム名を読み取って、Treasureオブジェクトを作成しています。Treasureオブジェクトは、Mapのeventsに追加しています。

宝箱

次に宝箱を表すTreasureクラスです。

class Treasure():
    """宝箱"""
    def __init__(self, pos, item):
        self.x, self.y = pos[0], pos[1]  # 宝箱座標
        self.mapchip = 46  # 宝箱は46
        self.image = Map.images[self.mapchip]
        self.rect = self.image.get_rect(topleft=(self.x*GS, self.y*GS))
        self.item = item  # アイテム名
    def open(self):
        """宝箱をあける"""
        sounds["treasure"].play()
        # TODO: アイテムを追加する処理
    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 "TREASURE,%d,%d,%s" % (self.x, self.y, self.item)

マップチップIDは46番の宝箱の画像で固定し、imageをロードしてます。またitemにアイテム名を格納しておきます。open()はプレイヤーが宝箱を開けたとき呼び出されるメソッドです。今は宝箱を開ける効果音を鳴らしているだけですが、後でプレイヤーの持ち物にアイテムを追加する処理も入れるかもしれません。draw()は宝箱の画像を座標に描画してるだけです。

次に扉です。

class Door:
    """とびら"""
    def __init__(self, pos):
        self.x, self.y = pos[0], pos[1]
        self.mapchip = 45
        self.image = Map.images[self.mapchip]
        self.rect = self.image.get_rect(topleft=(self.x*GS, self.y*GS))
    def open(self):
        """とびらをあける"""
        sounds["door"].play()
    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 "DOOR,%d,%d" % (self.x, self.y)

宝箱とほとんど同じですね。

一般オブジェクト

次に一般オブジェクトです。

class Object:
    """一般オブジェクト"""
    def __init__(self, pos, mapchip):
        self.x, self.y = pos[0], pos[1]
        self.mapchip = mapchip
        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 "OBJECT,%d,%d,%d" % (self.x, self.y, mapchip)

これも宝箱や扉とほとんど同じです。このように中身がほとんど同じ場合は継承というテクニックが使えます。Treasure、Door、ObjectのスーパークラスとしてEventを用意し、TreasureとDoorとObjectはEventのサブクラスにするわけです。ですが、ここではまだ使いません。

調べると開ける

最後にプレイヤーがスペースキーを押すと宝箱や扉が開く処理を見てみます。main()のイベントハンドラを見てください。

        for event in pygame.event.get():
            if event.type == QUIT:
                sys.exit()
            if event.type == KEYDOWN and event.key == K_ESCAPE:
                sys.exit()
            if event.type == KEYDOWN and event.key == K_SPACE:
                if msgwnd.is_visible:
                    # メッセージウィンドウ表示中なら次ページへ
                    msgwnd.next()
                else:
                    # 宝箱を調べる
                    treasure = player.search(map)
                    if treasure != None:
                        treasure.open()
                        msgwnd.set(u"%s をてにいれた。" % treasure.item)
                        map.remove_event(treasure)
                        continue
                    # とびらを開ける
                    door = player.open(map)
                    if door != None:
                        door.open()
                        map.remove_event(door)
                        continue
                    # はなす
                    chara = player.talk(map)
                    if chara != None:
                        msgwnd.set(chara.message)
                    else:
                        msgwnd.set(u"そのほうこうには だれもいない。")

スペースキーを押したときに宝箱を開ける、扉を開ける、話すの順に処理を追加してます。今のところコマンドウィンドウはないのでスペースキーで全部の処理を行っています。ここで注意してほしいんですが、宝箱を開けた場合、continueでぬけるので扉を開けたり、話したりする処理は実行されません。宝箱は最優先するのがポイントです(笑)では1つずつ見ていきます。

まず宝箱ですが、Playerのsearch()を呼び出して足下に宝箱イベントがあるか調べています。もし宝箱イベントがある場合、 treasure != Noneが成り立つのでif文が実行されます。Treasureのopen()を呼び出し、アイテムを手に入れたメッセージを表示しています。最後に Mapから宝箱イベントを削除しています。search()は足下に宝箱があるか調べる簡単なメソッドです。

    def search(self, map):
        """足もとに宝箱があるか調べる"""
        event = map.get_event(self.x, self.y)
        if isinstance(event, Treasure):
            return event
        return None

次に扉ですが、Playerのopen()を呼び出して目の前に扉イベントがあるか調べています。もし扉イベントがある場合、door != Noneが成り立つのでif文が実行されます。Doorのopen()を呼び出し、扉をマップから消します。これで開きますね。open()は目の前に扉があるか調べる簡単なメソッドです。

    def open(self, map):
        """目の前にとびらがあるか調べる"""
        # 向いている方向のとなりの座標を求める
        nextx, nexty = self.x, self.y
        if self.direction == DOWN:
            nexty = self.y + 1
        elif self.direction == LEFT:
            nextx = self.x - 1
        elif self.direction == RIGHT:
            nextx = self.x + 1
        elif self.direction == UP:
            nexty = self.y - 1
        # その場所にとびらがあるか?
        event = map.get_event(nextx, nexty)
        if isinstance(event, Door):
            return event
        return None

もし宝箱も扉もなければキャラクターと話そうとします。いなければ「だれもいない」です。ここら辺は混雑してますが、あとでコマンドウィンドウを実装するとすっきりします。

さあ王様がほうびをくれるそうなので宝物庫の扉を開けて宝箱を開けてみてください。まあアイテムを実装してないので手に入りませんが(笑)あと宝箱を開けたあとフィールドに出てまたお城に入ると宝箱が復活します。これは仕様です。マップのイベントファイルは宝箱を開けても何の変化もないのでイベントを再ロードすると復活してしまうのです。ここはあとでイベントフラグを追加すると解決するはずです。