4. マップ表示の基礎知識

はじめに

マップを表示する方法を調べていたのですが、思ったよりややこしいので、まずは情報を整理します。

基本知識

まず、Tileset(マップチップ=画像)とTilemap(マップデータ)があります。マップデータには地形の他にも、衝突判定をしたり、キャラの配置などの情報も必要で、それらはLayerで管理します。

また、マップ作成には、Tiledという別アプリを使うことが一般的なようです。(Phaser Editorでマップ作成する方法もあります。)

  • Tilemap : タイル配置情報。Tiled で作成し、Phaser に JSON で読み込む。
  • Tileset : タイル画像(マップチップ)。Tiled で切り出す元画像。
  • Layer : Tiled 上で地形/衝突/オブジェクトを分ける単位。Phaser 上も同名で扱う。

マップスクロールは、Cameraを使います。プレイヤーにカメラを固定するだけで、簡単にスクロールは実現します。

Phaser Editorのシーンエディタを使う方法

GUIでシーンにTilemapなどのオブジェクトを追加していきます。 マップ表示には、TilemapとEditable Tilemapの2つが用意されています。 最終的にはScene.tsのコードを出力して、それをプロジェクトで使うことになります。

1. Tilemap

  1. Tiledという別アプリを使ってマップを作成
  2. JSONでエクスポート
  3. シーンエディタにTileset、Tilemapを追加
  4. (他にもあれこれ追加して)
  5. Scene.tsを出力
  6. プロジェクトに追加して使う

という流れになります。

2. Editable Tilemap

こちらは別アプリを使わず、Phaser Editor内でマップ作成ができます。 その後の流れは同じですが、マップデータはJSONではなくコード内に埋め込まれてしまいます。

// Editable Tilemapのコード例
this.cache.tilemap.add("editabletilemap_e648ec1d-81f3-4e9c", {
  data: {
    layers: [
      {
        type: "tilelayer",
        name: "layer",
        data: [50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 
          50, 50, 50, 50, ...],
        // ...
      },
    ],
  },
});

マップデータをコードに埋め込むと、後で大変なことになるのは目に見えています。 さすがにこれはナシでしょう。(なんでこんな仕様にした???)

結論 → シーンエディタは使わない

どちらの方法でも、シーンエディタで出力したコード(Scene.ts)に、さらに自分のコードを追加していくわけなので、後からシーンを修正したい場合には大変になりそうです。 シーンエディタを使うのは、あまり良い方法とは思えません。

自分でコードを書く方法

Phaser Editorのシーンエディタを使わない方法です。作業の流れは以下の通り。

  1. Tiled でマップ作成
    • レイヤ1: terrain(地形)
    • レイヤ2: collision(通行不可)
    • レイヤ3: objects(プレイヤー初期位置・NPC・ワープなど)
  2. JSON でエクスポート
  3. Phaser 側にアセットを読み込む
    • AssetPackを使用 or ファイルを指定して読み込み
  4. Tilemap レイヤーを作成して描画&衝突設定
    • createLayer, setCollisionByProperty
  5. プレイヤー生成、カメラ追従、移動処理

コード例

// 基本コード例
create() {
  const map = this.make.tilemap({ key: "world" });
  const tiles = map.addTilesetImage("overworld", "tiles");

  const ground = map.createLayer("terrain", tiles); // 地形
  const collision = map.createLayer("collision", tiles)
                       .setCollisionByProperty({ collides: true }); // 衝突

  const spawn = map.findObject("objects", o => o.name === "playerSpawn");
  this.player = this.physics.add.sprite(spawn.x!, spawn.y!, "hero");

  this.physics.add.collider(this.player, collision);
  this.cameras.main.startFollow(this.player); // カメラ追従
}

シーンエディタでも、同じようなコードが出力されるので、自分で書いた方がよさそうです。

また、Asset Packを使わず、直接ファイルを読み書きした方が便利なような気がしてきました。 せっかく課金したのにPhaser Editorを使う理由がどんどん減っていきます😇

Tips

その他、調べている中で知ったことをメモしておきます。

16x16の画像を4倍に拡大したい

キャラやマップチップは16x16ピクセルを使っているので、4倍に拡大して表示したい場合はどうすればいいのか?

前回のプレイヤーの表示の際には、sprite.setScale(4)としていましたが、今後マップチップなど全てに設定するのは大変です。

  • ゲーム全体で設定する場合 → GameConfig.zoomを設定
  • シーンごとに設定する場合 → Camera.setZoom()を使う

スクロールの種類を変更する

プレイヤー中心でスクロールするのか、画面端に近づくとスクロールするのかを指定する方法です。

this.cameras.main.startFollow(this.player, true); // true: 滑らかに追従
this.cameras.main.setLerp(0.2, 0.2); // 追従のなめらかさ
this.cameras.main.setDeadzone(80, 60); // キャラが画面中心からズレる範囲(デッドゾーン)

2D ライトエフェクトを使う

ダンジョンなどプレイヤーの周りが明るくなる処理はLight2Dというパイプラインで簡単にできるようです。

ground.setPipeline("Light2D");
this.lights.enable().setAmbientColor(0x202020);
this.lights.addLight(this.player.x, this.player.y, 150).setColor(0xffffff);

まとめ

というわけで、マップ表示について調べていました。思ったより大変そう。 次回は実装していきます。