人工知能に関する断創録

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

マップスクロール

今回は少し大きめのマップを読み込んでみます。画面内には収まりきらないためプレイヤーの移動に応じて画面をスクロールする必要が出てきます。マップスクロールの方針は、

  1. マップ全体(キャラクターやアイテム含む)をSurfaceに描画
  2. オフセットに合わせてマップ全体の一部分を画面に描画

という2段階です。この方法は効率悪いかもしれませんが。とりあえずこれでいきます。もっと効率のよい実装方法があったら教えてください。

map_scroll.zip
f:id:aidiary:20100812211558p:plain

大きめのマップ

今回は少し大きめのマップを読み込みます。下のような感じです。前回と同じくBはブロックです。小部屋や落とし穴とかあって地下ダンジョンっぽいのを作ってみました。これくらい大きいマップだと前回までのように画面内に全体を描画できなくなります。そんなときはスクロールが必要です。

(注)等幅フォントでないと崩れます

   BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
   B                  B                   B
   B                  B                   B
   B                  B                   B
   B                  B                   B
   B                  B                   B
   B                  BB                  B
   B                   BBBBB      BBBBBBBBB
   B                                      B
   B                  BBBBBBBB  BBBBBBBBB B
   B                  BBBBBBBB  BBBBBBBBB B
   B                  BBBBBBBB  BBBBBBBBB B
   B                  BBBBBBBB  BBBBBBBBB B
   B                  BBBBBB      BBBBBBB B
   B        BBBBBBBBBBBBBBBB      BBBBBBB B
   B   B              BBBBBB      BBBBBBB B
   B                              BBBBBBB B
   B           BBBBBBBBBBBBBBBBBBBBBBBBBB B
   B                  BBBBBBBBBBBBBBBBBBB B
   B      B           BBBBBBBBBBBBBBBBBBB B
   B                                      B
   B   B             BBBB                 B
   B                BBBBBB                B
   BB    BBBBBB    BBBBBBBB               B
   B              BBBBBBBBBB              B
   BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB

マップスクロール

マップスクロールの原理はRPG編のタイルベーススクロール(2008/6/7)と同じです。ただタイル移動を考えなくていい分簡単です。マップ全体を描画できないならプレイヤーの位置に応じてマップの一部分だけ画面に描画すればいいわけです。下の図を見てください。

f:id:aidiary:20100812211559p:plain

大きな四角形がマップ全体を表しています。上で作ったマップですね。でゲーム画面の大きさは赤い四角形です。赤い四角形の中にマップ全体は収まらないのでマップの一部分だけ表示するようにします。ではマップのどの一部分を表示すればいいでしょう?ゲームを動かしてみるとわかりますがプレイヤーはスクロール中は画面の真ん中にいます。つまり、プレイヤーを中心としてその周りのマップを画面に表示させてやればいいですね。プレイヤーの動きに合わせて画面の表示される範囲(赤い四角形)は動きます。

オフセットの計算

マップのどの一部分を画面に描画するかを計算するためにオフセットの計算が必要になります。オフセットは上の図の赤い四角形の左上の座標に当たります。マップ全体の左上を(0,0)としたときの赤い四角形の左上の座標です。これはMapクラスのcalc_offset()で計算しています。

    def calc_offset(self):
        """オフセットを計算"""
        offsetx = self.python.rect.topleft[0] - SCR_RECT.width/2
        offsety = self.python.rect.topleft[1] - SCR_RECT.height/2
        return offsetx, offsety

self.python.rect.topleft[0]はプレイヤー(パイソン)のX座標、self.python.rect.topleft[1]はプレイヤーのY座標です。後は上の図と比べてみればわかりますね。前回もちょっと書いたのですが、self.map.surfaceには画面全体を描画しています。PyActionのdraw()ではマップ全体からオフセッとを用いてマップの一部分を画面に描画しています。

    def draw(self, screen):
        self.map.draw()
        
        # オフセッとに基づいてマップの一部を画面に描画
        offsetx, offsety = self.map.calc_offset()
        
        # 端ではスクロールしない
        if offsetx < 0:
            offsetx = 0
        elif offsetx > self.map.width - SCR_RECT.width:
            offsetx = self.map.width - SCR_RECT.width
        
        if offsety < 0:
            offsety = 0
        elif offsety > self.map.height - SCR_RECT.height:
            offsety = self.map.height - SCR_RECT.height
        
        # マップの一部を画面に描画
        screen.blit(self.map.surface, (0,0), (offsetx, offsety, SCR_RECT.width, SCR_RECT.height))

前回と比べてみます。

        # マップサーフェイスをスクリーンに描画
        screen.blit(self.map.surface, (0,0), (0,0,SCR_RECT.width, SCR_RECT.height))
                            ↓
        # マップの一部を画面に描画
        screen.blit(self.map.surface, (0,0), (offsetx, offsety, SCR_RECT.width, SCR_RECT.height))

オフセットを使って描画範囲を絞り込んでいます。意味は、マップ全体を描画したself.map.surfaceから(offsetx, offsety, SCR_RECT.width, SCR_RECT.height)の範囲(上の図の赤い四角形の範囲)を切り取って画面の(0,0)に描画しろという意味です。これでプレイヤーを中心としてそのまわりのマップだけが画面に描画されます。

マップの端っこでは

ゲームを動かしてみるとわかりますがマップの端っこではスクロールしないようにしました。マップの端っこに行くと赤い四角形がマップ全体からはみ出して下のif文の条件が成り立つようになります。その場合は、オフセットを調整して赤い四角形がマップ全体からはみ出さないように調整してやります。

        # 端ではスクロールしない
        if offsetx < 0:  # 赤い四角形が左にはみ出した場合
            offsetx = 0
        elif offsetx > self.map.width - SCR_RECT.width:  # 赤い四角形が右にはみ出した場合
            offsetx = self.map.width - SCR_RECT.width
        
        if offsety < 0:  # 赤い四角形が上にはみ出した場合
            offsety = 0
        elif offsety > self.map.height - SCR_RECT.height:  # 赤い四角形が下にはみ出した場合
            offsety = self.map.height - SCR_RECT.height

これで比較的大きなマップを作れるようになりました。ただ上のようにテキストマップを自分で書くのは大変です。RPGで作ったようなマップエディタが必要になりますね。