Pyxel 背景の処理(タイルマップ)

タイルマップを活用する

 Python向けレトロゲームエンジン「Pyxel(ピクセル)」の公式サンプルでゲームの背景の処理を学びます。タイルマップの使い方の工夫を見てみましょう。

 ・マップのある横スクロールアクションゲーム
  10_platformer.py
  assets/platformer.pyxres
 【ソースコードpyxel/python/pyxel/examples/10_platformer.py at main · kitao/pyxel · GitHub
 【デモ】https://kitao.github.io/pyxel/wasm/examples/10_platformer.html

  
  スペースキーを連続で押して空を飛ぼう

 ※前回の記事「Pyxel 背景の処理(イメージバンクの利用) - 勉強ボックス管理者ブログ」では自動的にスクロールする背景の処理を説明しています。

 ※タイルマップって何?という人は「game/docs/specs_img.md at main · benkyoubox/game · GitHub」にイメージバンクとタイルマップの関係の説明を書いたので参考にしてください。

 

リソースファイルの内容

 <イメージバンク>

 <タイルマップ>

 

背景の描画のポイント

 タイルマップの活用の仕方を見てみましょう。

01 地形と背景を重ねる

 サンプルのタイルマップは「壁や床などの地形」と「背景用の暗い柱」の2種類が用意されています。

 
 ソースコードでの表示処理は先に奥になる背景を描画して,その上に地形を(透明色を指定して)描画しています。
10_platformer.py 抜粋

class App:
    def draw(self):
        pyxel.cls(0)

        # Draw level
        pyxel.camera()
        pyxel.bltm(0, 0, 0, (scroll_x // 4) % 128, 128, 128, 128)
        pyxel.bltm(0, 0, 0, scroll_x, 0, 128, 128, TRANSPARENT_COLOR)

 ・bltm()命令を同じxy座標に2回実行。
 

 

02 キャラクター移動に合わせた背景スクロール

 奥の暗い柱の切り出し開始位置 u座標を scroll_x に合わせて増やし,0-127を繰り返すことでいつまでもスクロールできるようになっています。

pyxel.bltm(0, 0, 0, (scroll_x // 4) % 128, 128, 128, 128)

※画面幅の2倍のサイズの絵になるようにタイルマップが作成されています。
 Pyxel 背景の処理(イメージバンクの利用) - 勉強ボックス管理者ブログ では同一画像を並べて画面幅の2倍の絵にして,表示先の座標をフレーム更新数に応じて変化させていました。

 

03 壁タイルと床タイルの判定の工夫

 キャラクター移動時の衝突判定にも工夫があります。下図の右側が壁タイル,左上(1,0)が床タイルです。

 ソースコードではプレイヤーと敵キャラクターの移動時に,push_back(x座標, y座標, x移動量, y移動量) で移動先に動けるかどうか確認していますが,push_back()内でさらに1ドット移動ごとにタイルとの衝突判定 detect_collision() を行っています。

TILE_FLOOR = (1, 0)
WALL_TILE_X = 4

def detect_collision(x, y, dy):
    x1 = x // 8
    y1 = y // 8
    x2 = (x + 8 - 1) // 8
    y2 = (y + 8 - 1) // 8
    for yi in range(y1, y2 + 1):
        for xi in range(x1, x2 + 1):
            if get_tile(xi, yi)[0] >= WALL_TILE_X:
                return True
    if dy > 0 and y % 8 == 1:
        for xi in range(x1, x2 + 1):
            if get_tile(xi, y1 + 1) == TILE_FLOOR:
                return True
    return False

・detect_collision() での壁タイル(WALL_TILE_X)判定は,タイル情報 (tile_x, tile_y) の tile_x が 4 以上の場合Trueになります(get_tile(xi, yi)[0] の[0] で tile_x 要素を指定)。イメージバンク上の位置を決めて壁タイルを用意することで,壁タイルをまとめて判定できる工夫です。

・床タイルは上方向には通り抜けられるようになっています。床タイル(TILE_FLOOR)との判定は,dy > 0 のとき(y移動量が正:下方向への移動)かつ,足元のタイル8ドットの1段目を調べるときだけという条件が付いています。この条件で頭だけ引っかかって動けないなどを防いでいます。 
 

 

04 敵キャラクター出現位置

 下図の右上のボールのように,タイルマップの中に敵キャラクターの出現位置を決めているタイルが配置されています。
 
 イメージバンク上の位置

 ソースコードでは初期処理でイメージバンク上の絵を矩形で塗りつぶして,見た目は何もないようにして,敵キャラクターの出現処理にタイル情報だけ使用しています。

TILE_SPAWN1 = (0, 1)
TILE_SPAWN2 = (1, 1)
TILE_SPAWN3 = (2, 1)

def spawn_enemy(left_x, right_x):
    left_x = pyxel.ceil(left_x / 8)
    right_x = pyxel.floor(right_x / 8)
    for x in range(left_x, right_x + 1):
        for y in range(16):
            tile = get_tile(x, y)
            if tile == TILE_SPAWN1:
                enemies.append(Enemy1(x * 8, y * 8))
            elif tile == TILE_SPAWN2:
                enemies.append(Enemy2(x * 8, y * 8))
            elif tile == TILE_SPAWN3:
                enemies.append(Enemy3(x * 8, y * 8))

・・・
class App:
    def __init__(self):
        ・・・

        # Change enemy spawn tiles invisible
        pyxel.image(0).rect(0, 8, 24, 8, TRANSPARENT_COLOR)

・spawn_enemy()
 指定された幅の範囲内のタイルを全て調べて敵出現位置のタイルであれば,対応した敵クラスのインスタンスを作成しています。

・Appクラスの__init__()内
 pyxel.image(0).rect() でイメージバンクを対象として図形を描画できます。

※下記の違いがあることに注意してください。

pyxel.rect( ... ) ← 画面への描画
pyxel.image(0).rect( ... ) ← イメージバンク0への描画

こういった処理を使いこなすと,ゲームの進行に応じてイメージバンクのドット絵を更新することもできます。敵キャラクターの出現位置をタイルマップで指定する方法と合わせて,こんな事ができるんだと知っておくと役立つかもしれません。

 
タイルマップ活用例の紹介は以上です。奥行きのあるゲーム画面が作りたい人は,サンプルコードを参考にして色々と試してみてください。

 

関連記事

kinutani.hateblo.jp