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

人工知能に関する断創録

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



流れるメッセージ

Pygame

MessageWindowをもう少し改良してドラクエのようにメッセージが流れるようにアニメーションで表示されるようにします。これは思ったより簡単です。今までアニメーションするときどうしてたか思い出してください。フレームごとに状態を更新するupdate()があればいいですね!

pyrpg16.zip
f:id:aidiary:20100731154457p:plain

メッセージウィンドウの更新

まずはMessageWindowのコンストラクタから始めます。

    def __init__(self, rect):
        Window.__init__(self, rect)
        # テキストを表示する矩形
        self.text_rect = self.inner_rect.inflate(-32, -32)
        self.text = []  # メッセージ
        self.cur_page = 0  # 現在表示しているページ
        self.cur_pos = 0  # 現在ページで表示した最大文字数
        self.next_flag = False  # 次ページがあるか?
        self.hide_flag = False  # 次のキー入力でウィンドウを消すか?
        self.msg_engine = MessageEngine()  # メッセージエンジン
        self.cursor = load_image("cursor.png", -1)  # カーソル画像
        self.frame = 0

前回までなかったself.frameとself.cur_posがポイントです。self.cur_pageは現在表示中のページを表していましたが、self.cur_posはself.textの何文字目まで表示中かを表す変数です。このself.cur_posをMessageWindowのupdate()が呼ばれるたびに+1で更新することで表示される範囲が長くなり、文字が流れてるように見えます。次にupdate()を見てみます。

    def update(self):
        """メッセージウィンドウを更新する
        メッセージが流れるように表示する"""
        if self.is_visible:
            if self.next_flag == False:
                self.cur_pos += 1  # 1文字流す
                # テキスト全体から見た現在位置
                p = self.cur_page * self.MAX_CHARS_PER_PAGE + self.cur_pos
                if self.text[p] == "/":  # 改行文字
                    self.cur_pos += self.MAX_CHARS_PER_LINE
                    self.cur_pos = (self.cur_pos/self.MAX_CHARS_PER_LINE) * self.MAX_CHARS_PER_LINE
                elif self.text[p] == "%":  # 改ページ文字
                    self.cur_pos += self.MAX_CHARS_PER_PAGE
                    self.cur_pos = (self.cur_pos/self.MAX_CHARS_PER_PAGE) * self.MAX_CHARS_PER_PAGE
                elif self.text[p] == "$":  # 終端文字
                    self.hide_flag = True
                # 1ページの文字数に達したら▼を表示
                if self.cur_pos % self.MAX_CHARS_PER_PAGE == 0:
                    self.next_flag = True
        self.frame += 1

制御文字があるので少し複雑ですが、ポイントはself.cur_posを+1していることです。set()で制御文字があるたびにpを移動させていましたが、今回はpではなく、self.cur_posを移動させるだけで移動のさせ方は同じです。また今回はset()で終端文字$をメッセージの最後に追加しています。update()で$が見つかるとself.hide_flagをTrueにしてメッセージが最後まで表示されたことを記録しておきます。このupdate()は毎フレーム呼び出されます。呼び出されるたびにself.cur_posが+1される仕掛けです。次にnext()も見ておきます。

    def next(self):
        """メッセージを先に進める"""
        # 現在のページが最後のページだったらウィンドウを閉じる
        if self.hide_flag:
            self.hide()
        # ▼が表示されてれば次のページへ
        if self.next_flag:
            self.cur_page += 1
            self.cur_pos = 0
            self.next_flag = False

先ほど説明したようにメッセージの最後までたどり着くとself.hide_flagがTrueになります。その状態でnext()を呼び出すとメッセージウィンドウをhide()で消します。self.hide_flagがFalseでself.next_flagがTrueなら次ページがあるのでnext()で次ページへ進めます。

1文字ずつ描画

次にメッセージを描画するdraw()を見てみます。前回は、1ページ分まるごと描画しましたが、今回はself.cur_posまでの文字列を描画します。self.cur_posは1フレームごとに+1されるのでdraw()で表示されるメッセージは1フレームごとに長くなっていきます。これによってメッセージが流れているように見えるわけです。

    def draw(self, screen):
        """メッセージを描画する
        メッセージウィンドウが表示されていないときは何もしない"""
        Window.draw(self, screen)
        if self.is_visible == False: return
        # 現在表示しているページのcur_posまでの文字を描画
        for i in range(self.cur_pos):
            ch = self.text[self.cur_page*self.MAX_CHARS_PER_PAGE+i]
            # 制御文字は表示しない
            if ch == "/" or ch == "%" or ch == "$": continue
            dx = self.text_rect[0] + MessageEngine.FONT_WIDTH * (i % self.MAX_CHARS_PER_LINE)
            dy = self.text_rect[1] + (self.LINE_HEIGHT+MessageEngine.FONT_HEIGHT) * (i / self.MAX_CHARS_PER_LINE)
            self.msg_engine.draw_character(screen, (dx,dy), ch)
        # 最後のページでない場合は▼を表示
        if (not self.hide_flag) and self.next_flag:
            if self.frame / self.animcycle % 2 == 0:
                dx = self.text_rect[0] + (self.MAX_CHARS_PER_LINE/2) * MessageEngine.FONT_WIDTH - MessageEngine.FONT_WIDTH/2
                dy = self.text_rect[1] + (self.LINE_HEIGHT + MessageEngine.FONT_HEIGHT) * 3
                screen.blit(self.cursor, (dx,dy))

▼を点滅させる点にも注意してください。これは、キャラクターアニメーション(2008/5/18)で説明した方法と同じです。animcycleを定義してフレームごとに表示、非表示を切り替えています。表示、非表示を切り替えれば点滅しているように見えますね。最後にmain()のゲームループでMessageWindowのupdate()を呼び出すのを忘れないでください。

これでドラクエのように流れるメッセージウィンドウができました。ただこれでも少し違いますね。ドラクエは行ごとに上に流れているからです。まあとりあえずページごとに流れる方法で止めておきます。ここら辺はちょっと複雑というか面倒くさい処理でした。次回からはもっと簡単です。