人工知能に関する断創録

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

コマンドウィンドウ

コマンドウィンドウを実装します。メッセージウィンドウの表示で説明したWindowクラスを拡張してCommandWindowクラスを作ります。コマンドは、はなす、つよさ、そうび、とびら、じゅもん、どうぐ、さくせん、しらべるの8つです。ドラクエと同じか(笑)スペースキーでコマンドウィンドウが開くので矢印キーでコマンドを選択しもう一度スペースキーでコマンド実行です。

pyrpg23.zip
f:id:aidiary:20100807222508p:plain

コマンドウィンドウの外観

メッセージウィンドウの詳細(2008/6/28)でもそうでしたが、今回もウィンドウサイズをきっちり設計しておきます。コマンドウィンドウは下のようなサイズにしました。

f:id:aidiary:20100807222509p:plain

実際にテキストを描画する矩形のサイズは、176x120です。ウィンドウのサイズは余白を考慮して逆算した結果216x160になりました。

    # コマンドウィンドウ
    cmdwnd = CommandWindow(Rect(16,16,216,160), msg_engine)

なぜこのサイズにしたかというとコマンドの文字列を下のように格納したかったからです。

▶はなす__▶じゅもん
▶つよさ__▶どうぐ_
▶そうび__▶さくせん
▶とびら__▶しらべる

_は空白で▶はコマンド選択のカーソルが入る場所です。こうすると縦方向に4文字、横方向に11文字ですね。会話する(2008/6/28)で説明したように文字のサイズは行間を入れて縦30ピクセル、横16ピクセルです。つまり、上のように格納したいときは縦30x4=120ピクセル、横16x11=176ピクセルあればいいです。これは、text_rectの大きさですね!

CommandWindowクラス

じゃ、上のようなコマンドウィンドウを実装するクラスCommandWindowを作ります。

class CommandWindow(Window):
    LINE_HEIGHT = 8  # 行間の大きさ
    TALK, STATUS, EQUIPMENT, DOOR, SPELL, ITEM, TACTICS, SEARCH = range(0, 8)
    COMMAND = [u"はなす", u"つよさ", u"そうび", u"とびら",
               u"じゅもん", u"どうぐ", u"さくせん", u"しらべる"]
    def __init__(self, rect, msg_engine):
        Window.__init__(self, rect)
        self.text_rect = self.inner_rect.inflate(-32, -32)
        self.command = self.TALK  # 選択中のコマンド
        self.msg_engine = msg_engine
        self.cursor = load_image("data", "cursor2.png", -1)
        self.frame = 0
    def draw(self, screen):
        Window.draw(self, screen)
        if self.is_visible == False: return
        # はなす、つよさ、そうび、とびらを描画
        for i in range(0, 4):
            dx = self.text_rect[0] + MessageEngine.FONT_WIDTH
            dy = self.text_rect[1] + (self.LINE_HEIGHT+MessageEngine.FONT_HEIGHT) * (i % 4)
            self.msg_engine.draw_string(screen, (dx,dy), self.COMMAND[i])
        # じゅもん、どうぐ、さくせん、しらべるを描画
        for i in range(4, 8):
            dx = self.text_rect[0] + MessageEngine.FONT_WIDTH * 6
            dy = self.text_rect[1] + (self.LINE_HEIGHT+MessageEngine.FONT_HEIGHT) * (i % 4)
            self.msg_engine.draw_string(screen, (dx,dy), self.COMMAND[i])
        # 選択中のコマンドの左側に▶を描画
        dx = self.text_rect[0] + MessageEngine.FONT_WIDTH * 5 * (self.command / 4)
        dy = self.text_rect[1] + (self.LINE_HEIGHT+MessageEngine.FONT_HEIGHT) * (self.command % 4)
        screen.blit(self.cursor, (dx,dy))
    def show(self):
        """オーバーライド"""
        self.command = self.TALK  # 追加
        self.is_visible = True

ポイントは、各コマンドをTALK、STATUSなどの定数にしている点です。commandには現在選択中のコマンド定数が入ります。コマンドウィンドウを表示するときはshow()が呼ばれるのでTALKが選択状態になるように初期化しています。カーソルは点滅させてもよかったのですが見にくかったので止めました。

コマンドウィンドウを開く

コマンドウィンドウを表示する処理はmain()のイベントハンドラにあります。コマンドウィンドウが表示されているときは cmdwnd_handler()に処理をまかせています。まだウィンドウを開いていないときはcmdwndのshow()を呼び出して表示しています。

    # イベントハンドラ
    for event in pygame.event.get():
        if event.type == QUIT:
            sys.exit()
        if event.type == KEYDOWN and event.key == K_ESCAPE:
            sys.exit()
        # 表示されているウィンドウに応じてイベントハンドラを変更
        if cmdwnd.is_visible:
            cmdwnd_handler(event, cmdwnd, msgwnd, player, map)
        elif msgwnd.is_visible:
            msgwnd.next()  # 次ページへ
        else:
            if event.type == KEYDOWN and event.key == K_SPACE:
                sounds["pi"].play()
                cmdwnd.show()

次にcmdwnd_handler()です。このハンドラはコマンドウィンドウが表示されているときキーボード入力を処理します。

def cmdwnd_handler(event, cmdwnd, msgwnd, player, map):
    """コマンドウィンドウが開いているときのイベント処理"""
    # 矢印キーでコマンド選択
    if event.type == KEYDOWN and event.key == K_LEFT:
        if cmdwnd.command <= 3: return
        cmdwnd.command -= 4
    elif event.type == KEYDOWN and event.key == K_RIGHT:
        if cmdwnd.command >= 4: return
        cmdwnd.command += 4
    elif event.type == KEYUP and event.key == K_UP:
        if cmdwnd.command == 0 or cmdwnd.command == 4: return
        cmdwnd.command -= 1
    elif event.type == KEYDOWN and event.key == K_DOWN:
        if cmdwnd.command == 3 or cmdwnd.command == 7: return
        cmdwnd.command += 1
    # スペースキーでコマンド実行
    if event.type == KEYDOWN and event.key == K_SPACE:
        if cmdwnd.command == CommandWindow.TALK:  # はなす
            sounds["pi"].play()
            cmdwnd.hide()
            chara = player.talk(map)
            if chara != None:
                msgwnd.set(chara.message)
            else:
                msgwnd.set(u"そのほうこうには だれもいない。")
        elif cmdwnd.command == CommandWindow.STATUS:  # つよさ
            # TODO: ステータスウィンドウ表示
            sounds["pi"].play()
            cmdwnd.hide()
            msgwnd.set(u"つよさウィンドウが ひらくよてい。")
        elif cmdwnd.command == CommandWindow.EQUIPMENT:  # そうび
            # TODO: そうびウィンドウ表示
            sounds["pi"].play()
            cmdwnd.hide()
            msgwnd.set(u"そうびウィンドウが ひらくよてい。")
        elif cmdwnd.command == CommandWindow.DOOR:  # とびら
            sounds["pi"].play()
            cmdwnd.hide()
            door = player.open(map)
            if door != None:
                door.open()
                map.remove_event(door)
            else:
                msgwnd.set(u"そのほうこうに とびらはない。")
        elif cmdwnd.command == CommandWindow.SPELL:  # じゅもん
            # TODO: じゅもんウィンドウ表示
            sounds["pi"].play()
            cmdwnd.hide()
            msgwnd.set(u"じゅもんウィンドウが ひらくよてい。")
        elif cmdwnd.command == CommandWindow.ITEM:  # どうぐ
            # TODO: どうぐウィンドウ表示
            sounds["pi"].play()
            cmdwnd.hide()
            msgwnd.set(u"どうぐウィンドウが ひらくよてい。")
        elif cmdwnd.command == CommandWindow.TACTICS:  # さくせん
            # TODO: さくせんウィンドウ表示
            sounds["pi"].play()
            cmdwnd.hide()
            msgwnd.set(u"さくせんウィンドウが ひらくよてい。")
        elif cmdwnd.command == CommandWindow.SEARCH:  # しらべる
            sounds["pi"].play()
            cmdwnd.hide()
            treasure = player.search(map)
            if treasure != None:
                treasure.open()
                msgwnd.set(u"%s をてにいれた。" % treasure.item)
                map.remove_event(treasure)
            else:
                msgwnd.set(u"しかし なにもみつからなかった。")

長いですが簡単です。まず、矢印キーを押したときにcmdwnd.commandを変えています。commandは先ほど説明したように現在選択中のコマンド定数です。コマンド定数は

 0はなす  4じゅもん
 1つよさ  5どうぐ
 2そうび  6さくせん
 3とびら  7しらべる

のように割り当てられていました。たとえば、はなすのところにカーソルがある状態(command=0)で右を押すとcommandは4にすればいいです。また、そうびのところにカーソルがある状態(command=2)で右を押すとcommandは6にすればいいです。つまり、右を押したときはcommandに4を足してやればいいわけです。プログラム中でもそうなってますね。しかし、commandが4以上のとき(じゅもん、どうぐ、さくせん、しらべる)は右を押しても移動できないのでそのままにしています。上、下、左を押したときも同様に考えるとプログラムを読み解けます。

次にスペースキーを押してコマンドを実行したときです。はなすのときはコマンドウィンドウを閉じてから今まで通りはなすの処理が書かれています。とびらとしらべるも同じですね。前回とは違って処理が分離されているのでわかりやすくなったと思います。はなす、とびら、しらべる意外はまだ未実装なので適当にメッセージを表示しています。