Phaser3 タイルマップ

phaser

Phaser3 タイルマップ

前回の記事

https://mijinc0.github.io/blog/post/20200821_phaser3_audio/

タイルマップエディタ

タイルマップは以下のようなデータに一つ一つ指定したタイルの画像を当てはめることでマップを作る。

[
  0, 0, 0, 4, 0, 3, 0, 0,
  0, 1, 0, 0, 0, 0, 0, 0,
  0, 1, 0, 0, 0, 0, 0, 0,
  0, 1, 1, 1, 1, 1, 0, 0,
  0, 0, 0, 0, 0, 3, 0, 0,
  0, 2, 0, 2, 0, 0, 0, 0,
]

ではこのデータ、どうやって作るのかという話になる。

1. Tiledを使う

https://www.mapeditor.org/

タイルマップを作成するのに素晴らしいツールがある。無償でも使えるが、支援できる人は少額でもしてあげてほしい。
Phaser3ではTiledでタイルマップを作ることがある程度想定された実装をしている。そのため、Tiledによって生成されたタイルマップのデータ(json)からレイヤーオブジェクトを生成するときはある程度よしなにしてくれる。

2. エディタを自作する

エディタを作るのも楽しみの一つだと思う人は作ってみても良いかもしれない。どこまでものを作るのかとなるとピンキリになると思うが、たとえばビットマップ等のイメージファイルのデータからタイルマップデータを作るツールを作って、タイルマップの描写自体はそのイメージが扱えるドット絵エディタで行うなどの方法も考えられる。イメージからデータを抽出するのはバイナリデータのフォーマットが分からなければライブラリを使う手がありそう。

3. 直打ちする

極めて非効率で大変だが直打ちしても良い。Phaser3のタイルマップ機能の練習をした時は、私はハードコードした直打ちのデータを使っていました。

タイルマップデータとして生データを使う時

タイルマップの作り方を調べていると、以下のような流れでマップのレイヤーデータを作ることになると思う。

// 適当なタイルマップの生データ
const data = [
  [ 0, 0, 0, 0, 0, 0, 0, 0 ],
  [ 0, 0, 0, 0, 0, 0, 0, 0 ],
  [ 0, 0, 0, 0, 0, 0, 0, 0 ],
  [ 0, 0, 0, 0, 0, 0, 0, 0 ],
];

// 1. create phaser's tilemap
const tilemap = this.make.tilemap({
  data: data,
  tileWidth: 32,
  tileHeight: 32,
});

// 2. create phaser's tileset
// もし描写してみて想定したのと違うタイルイメージが割り当てられたときは、一番最後のgidを調整してみよう。
const gid = 1;
const tileset = tilemap.addTilesetImage('tileset_image', undefined, tileSize.width, tileSize.height, 0, 0, gid);

// 3. create static layer
const layerId = 0;
return tilemap.createStaticLayer(layerId, tileset, 0, 0);

よくわからないのが、一番下のlayerIdだ。

結論から言うと、上記のように生データを指定した時は常にlayerId0である。これはTilemapクラスのインスタンスには複数のレイヤーデータを登録することが出来、その登録されたデータのインデックスを指定して生成するから。Tilemapクラスのインスタンスに複数のレイヤーが存在するのは、主にTiledからの出力データを元にTilemapインスタンスを生成した時だと思って良い。

Phaserの標準機能を使わずに自分でタイルマップを作る

用途によっては、Phaserのタイルマップを自分で作りたくなる時もあると思う。例えば、二次元の配列からPhaser.Tilemaps.Tilemapオブジェクトを生成したいが、複数のレイヤーをもたせ、なおかつそのオブジェクトをキャッシュに貯めておきたい場合など。

以下のようにして作る。

// this === Phaser.Scene

// テクスチャはtextureManagerからキャッシュにアクセス出来るので、そこからタイルマップのイメージを取得
const tilesetImage = this.textures.get('tilesetImage');

// タイルセットオブジェクトを作成し、イメージをセットする
const tileset = new Phaser.Tilemaps.Tileset('testTileset', 5, 40, 40, 0, 0);
tileset.setImage(tilesetImage);

// レイヤーデータを作成する
const layerDataA = new Phaser.Tilemaps.LayerData({
  name: 'testLayerA',
  tileWidth: 40,
  tileHeight: 40,
});

// 必要があればここでtileオブジェクトにプロパティをセットすれば後から入れなくて良い
// 自分でファクトリーを作るメリットでもある
const tile = new Phaser.Tilemaps.Tile(layerData, 6, 0, 0, 40, 40, 40 ,40);

const dataA = [
  [tile, tile, tile],
  [tile, tile, tile],
  [tile, tile, tile],
];

layerDataA.data = dataA;
layerDataA.width = dataA[0].length;
layerDataA.height = dataA.length;

// レイヤーデータを作成する(2つめ)
const layerDataB = new Phaser.Tilemaps.LayerData({
  name: 'testLayerB',
  tileWidth: 40,
  tileHeight: 40,
});

// 必要があればここでtileオブジェクトにプロパティをセットすれば後から入れなくて良い
// 自分でファクトリーを作るメリットでもある
const tile = new Phaser.Tilemaps.Tile(layerData, 6, 0, 0, 40, 40, 40 ,40);

const dataB = [
  [tile, tile, tile],
  [tile, tile, tile],
  [tile, tile, tile],
];

layerDataB.data = dataB;
layerDataB.width = dataB[0].length;
layerDataB.height = dataB.length;

// マップデータオブジェクトを作る
// ここで複数のレイヤーを登録しておけば、createStatic(Dynamic)Layerをする時に
// 各レイヤーのnameを指定して描写されるレイヤーオブジェクトを生成できるようになる
const mapdata = new Phaser.Tilemaps.MapData({
  width: 3,
  height: 3,
  tileWidth: 40,
  tileHeight: 40,
  widthInPixels: 40 * 3,
  heightInPixels: 40 * 3,
  layers: [layerDataA, layerDataB],
});

// これで完了
const tilemap = new Phaser.Tilemaps.Tilemap(this, mapdata);

// レイヤーオブジェクトを生成する時は、レイヤー名を指定して生成できる
const staticLayerA = tilemap.createStaticLayer('testLayerA', tileset);
const dynamicLayerB = tilemap.createDynamicLayer('testLayerB', tileset);

// キャッシュに登録しておけば、以上の手順が必要になるのは最初の一回だけで、
// 次からはグローバルにあるキャッシュから取り出してcreateStatic(Dynamic)Layerをするだけで良くなる
this.cache.tilemap.add('myTilemap', tilemap);

// 二回目以降の生成はこれだけ。最初にタイルマップを作ってキャッシュしてしまえば楽(要メモリと相談)
const cachedTilemapObject = this.cache.tilemap.get('myTilemap');
cachedTilemapObject.createStaticLayer('testLayerA', tileset);
cachedTilemapObject.createStaticLayer('testLayerB', tileset);

参考

https://medium.com/@michaelwesthadley/modular-game-worlds-in-phaser-3-tilemaps-1-958fc7e6bbd6