Phaser3 TileMapの基礎
主な改定履歴
- 2025/05/02 新規公開
はじめに
この記事では Tiled
というマップエディタを使用し、簡単な横スクロールゲームを作成します。
まず前半では Tiled
のソフトの使い方について解説します。
そのあと Phaser3
でのマップデータの扱い方と、コードの書き方について見ていきます。
この記事の前提条件
-
Phaser3
の基礎的な扱い方を知っていること。
(なお、Phaser3
のArcade
物理エンジンの基礎についてはこちらの別記事にて解説しています。) -
TypeScript
が分かること。
作成するゲームの内容
- 2D横スクロールアクションゲームを作ります。
- 矢印キーでプレイヤーを操作し、画面はプレイヤーに合わせてスクロールします。
- プレイヤーが黄色い鍵に接触すると鍵を取得します。
- プレイヤーが赤い宝石に接触するとポイントが入ります(演出のみ)。
- すべての鍵を取得し、ゴールの黄色い扉(ゴール)に到着するとゲームクリアです。
鍵を取りこぼしている場合はゴールできません。
この記事のソースはStackBlitzで確認できます。
StackBlitzの使い方
画面右下の Editor
をクリックし、左上のハンバーガーメニューからソースが参照できます。
タイルマップの作り方
Tiledについて
Tiledは、無料で利用できる汎用的なマップエディタです。
2Dの横スクロールアクションのステージをはじめ、
RPGでよく見るようなトップビューのマップなども簡単に作ることができます。
Tiledの公式サイト から最新版をダウンロードし、インストールします。
(本記事の随筆時点ではバージョン1.11.2
が最新です。)
アセットの準備
ゲームで使うアセット(資材)は、次のサイトで配布されている Simplified Platformer Pack
をもとに、一部加工して利用します。
Open Game Art - Simplified Platformer Pack
アセットを用意したら編集用のフォルダを決め、その中にタイルセット画像を置いて作業していきます。
プロジェクトファイル(tmx)とJSONファイルの保存先もこのフォルダを使って作業を進めます。
タイルマップの作成
ここからTiledを使って具体的なマップ作成の手順を見ていきます。
初期設定
アプリを起動したら、「新しいマップ」をクリックします。
作成するマップの初期設定を入力します。
今回使用するタイルの画像は1つあたり64x64
の大きさですので「タイルの大きさ」もその値で設定します。
タイルセットの追加
画面右下「新しいタイルセット」ボタンをクリックします。
ダイアログが出ますので、名前を tilesheet
にします。
このとき必ず「マップに埋め込み」をONにします。
準備したタイルシート画像ファイルを選択します。
必要に応じて、透過色の設定を行います。
OKを押すと「タイルセットビュー」に新しいタイルセットが表示されます。
メニューから「ファイル名を付けて保存」を選択し、プロジェクトをtmxファイルとしてタイルシート画像と同じフォルダに保存します。
ここではファイル名を tilesmap.tmx
とします。
タイルセットの編集
タイル要素ごとに必要なプロパティを登録します。
具体的にはアクションゲームで地面やブロックとなるタイル要素に対し、それ以外のタイルと区別がつくようカスタムプロパティを設定します。
「タイルセットビュー」のスパナのマーク「タイルセットを編集」をクリックします。
別タブで「タイルセット編集」画面が出ます。
タイルセットにあるタイルのうち「地面やブロックとなるタイル」を選択状態にします。
(shift+クリックやctrl+クリックで複数選択できます)
「プロパティビュー」の左下にある「+」のアイコン「プロパティを追加」ボタンをクリックします。
ダイアログが出ますので、プルダウンをbool
、名前にcollides
を指定します。
OKでダイアログを閉じたら、プロパティビューに行が追加されますので、チェックをONにします。
保存して「タイルセット編集」タブを閉じます。
レイヤーの編集
レイヤー一覧から「タイルレイヤー」を追加します。
リストにタイルレイヤーが2つある状態にし、それぞれ名前を platform
, background
とします。
platform
はプレイヤーが移動できる領域のマップです。
「スタンプ」や「塗りつぶし」ツールを使ってマップを作成します。
なお、ゲーム内のゴールとして使うので扉のタイルをマップ右端の方に配置します。
background
は背景だけのレイヤーです。
続いて、オブジェクトレイヤーを追加して鍵などのオブジェクトを配置します。
タイルレイヤーの追加の手順と同様に、リストから「オブジェクトレイヤー」を選択し追加します。
名前を objectsLayer
に変更します。
オブジェクトの配置
プレイヤーの配置
ツールバーから「点を追加」を選びます。
レイヤー objectsLayer
が選択されていることを確認し、マップの右端の方をクリックしオブジェクトを配置します。
名前を player
に変更します。
宝石・鍵の配置
ツールバーから「タイルを追加」をクリックします。
タイルセットから宝石を選択し、オブジェクトレイヤーの任意の場所に配置します。
プロパティ画面から配置したオブジェクトの名前を gem
に変更します。
プロパティ追加ボタンをクリックし、プルダウンをint
、名前を point
としてカスタムプロパティを追加します。
リストに行が追加されたら、point
の行に任意の値を入力します。
ツールバーから「オブジェクトを選択」をクリックし、配置した gem
を選択してコピー&ペーストを行い、宝石を複製します。
複製した宝石のpointは好みで変更できます。
宝石の配置と同様の手順で鍵のオブジェクトも配置します。
このとき名前は key
とし、カスタムプロパティは不要です。
ゴールの配置
ツールバーから「四角形を追加」をクリックし、ゴールの扉に合わせて、四角形を描画します。
配置した四角形の名前を goal
とします。
ここまででマップの作成手順は終了です。
JSONファイルのエクスポート
プロジェクトファイルを保存したら、メニューバー>ファイルから「名前をつけてエクスポート」をクリックします。
ファイル名を tilemap.json
とし、タイルシートと同じ場所にJSONファイルを出力します。
Phaser3 での利用準備
Phaser3
のプロジェクトフォルダの public
に以下のような形で、
tilemap.json
とタイルシートの画像 tilesheet.png
の2ファイルをコピーして配置します。
tmxファイルはゲーム実行時には不要です。
public
└─assets
├─images
└─maps
tilemap.json
tilesheet.png
コードの解説
タイルマップに関わる個所を中心に解説していきます。
(冒頭にも記した通り、Arcade
物理エンジンの基礎については別の記事にて解説しています。)
まず、preload
メソッドにおいて、
tilemapTiledJSON
メソッドでJSONファイルからタイルマップのデータを読み込みます。
タイルシートの画像は、通常の画像と同じように tilesheet.png
をLoadします。
(なお、今回のサンプルではタイルシートの一部をアイテムのスプライトとして使用するため、spritesheetメソッドを使用しています。
this.tilemap.addTilesetImageメソッドにテクスチャを渡すだけの場合は、this.load.imageメソッドで問題ありません。)
this.load.setPath('assets/');
this.load.tilemapTiledJSON('tilemap_key', 'maps/tilemap.json');
this.load.spritesheet('tilesheet_key', 'maps/tilesheet.png', { frameWidth: 64 });
this.load.spritesheet('player', 'images/charasheet.png', { frameWidth: 80, frameHeight: 74 });
続いて create
メソッドに移ります。
tilemap
メソッドでタイルマップのインスタンスを作成し、
addTilesetImage
メソッドで読み込み済みのテクスチャと紐づけます。
なお、今回分かり易いようにタイルマップ、タイルシートのCacheのキー名には末尾に key
と付けてあります。
addTilesetImage
メソッドに関して、第1引数はタイルマップ内のタイルシート名、
第2引数にタイルシートのテスクチャ名を指定ますが、同名の場合は第2引数は省略できます。
// タイルマップを作る
this.tilemap = this.make.tilemap({ key: 'tilemap_key' });
// タイルマップ内で定義されているタイルシートの名前と、画像のキーを紐づける。
// (なお、第2引数を省略した場合は、第1引数と同じ文字列が使われる。)
this.tileset = this.tilemap.addTilesetImage('tilesheet', 'tilesheet_key') as Phaser.Tilemaps.Tileset;
続いて、レイヤーの設定に移ります。
タイルマップ内で定義したレイヤー名を指定して、それぞれレイヤーオブジェクトを作成します。
// Tilemapから各レイヤーを作成する。
// createLayerの呼び出し順は表示に関係するので背景から作成する。
const background = this.tilemap.createLayer('background', this.tileset, 0, 0); // 背景
this.platform = this.tilemap.createLayer('platform', this.tileset, 0, 0) as Phaser.Tilemaps.TilemapLayer; // プラットフォーム
this.objectsLayer = this.tilemap.getObjectLayer("objectsLayer") as Phaser.Tilemaps.ObjectLayer; // オブジェクトレイヤー
プラットフォームレイヤーのうち、タイルシートでプロパティに collides
を設定したタイルを衝突検知対象にします。
// プラットフォームレイヤーのプロパティから衝突検知対象のタイルを抽出する。
this.platform.setCollisionByProperty({ collides: true });
オブジェクトレイヤーから名前が player
である要素を見つけて、その座標にプレイヤーのスプライトを配置します。
// プレイヤー
const playerPos = this.objectsLayer.objects.find(o => o.name === "player") as Phaser.Types.Tilemaps.TiledObject;
this.player = this.physics.add.sprite(playerPos.x, playerPos.y, 'player')
.setOrigin(0.5, 1).setBounce(0.1).setBodySize(54, 66);
// プレイヤーの移動範囲を画面の中だけとする。
this.player.setCollideWorldBounds(true);
ここからは宝石と鍵を画面に配置していきます。クラス内の deployItems
メソッドに処理をまとめています。
プレイヤーの時と同様に、オブジェクトレイヤーから指定の名前のオブジェクトを抽出します。
注意点として、タイルオブジェクトの原点(座標の基準)はタイル画像の左下なのに対し、
配置するスプライトの原点は中央にしますので、その差を考慮する必要があります。
そのあと、カスタムプロパティの取得を行います。
指定の名前のプロパティ、ここでは宝石のオブジェクトにある point
を取得し、その値をスプライトに保持します。
この値はプレイヤーが宝石を取得した時の表示に使われます。
// タイル1個あたりの横幅、高さを取得
const tileSize = { w: this.tileset.tileWidth, h: this.tileset.tileHeight };
// 指定の名前のオブジェクトのみを抽出する
const items = this.objectsLayer.objects.filter(o => o.name === item);
// 抽出したオブジェクトからスプライトを生成する
const sprites = items.map(e => {
const obj = e as Phaser.Types.Tilemaps.TiledObject;
// アイテムの作成
// オブジェクトレイヤーに配置したタイルは原点(Origin)が左下であるため、配置の際はx,yの値に注意。
const sprite = this.physics.add.sprite(
obj.x + tileSize.w / 2,
obj.y - tileSize.h / 2,
spritesheet, obj.gid - 1
).setOrigin(0.5);// tweenで動かす関係でspriteのOriginを0.5にする。
sprite.setName(obj.name);
sprite.setData('gid', obj.gid);
// オブジェクトからカスタムプロパティを取得しスプライトのデータとして保持する。
const customProps = obj.properties as CustomProp[];
if (propNames && customProps?.length > 0) {
propNames.forEach(name => {
const gotElm = customProps.find(f => f.name === name);
if (gotElm) {
sprite.setData(name, gotElm.value);
}
})
}
return sprite;
});
ゴールの設置をします。
基本的にはこれまでのタイルオブジェクトと同じですが、マップに描画した四角形の高さと幅を保持しているのでその値を使って四角形の配置をします。
// ゴール
const goalPos = this.objectsLayer.objects.find(o => o.name === "goal") as Phaser.Types.Tilemaps.TiledObject;
const goalRect = this.add.rectangle(goalPos.x, goalPos.y, goalPos.width, goalPos.height).setOrigin(0);
const goalSprite = this.physics.add.existing(goalRect);
const goalSpriteBody = goalSprite.body as Phaser.Physics.Arcade.Body;
最後にカメラ関係の処理について。
今回のゲームではカメラがプレイヤーに追跡するようにしますので、以下ようにコードを記述します。
// カメラ関連
this.cameras.main.setBounds(0, 0, this.tilemap.widthInPixels, this.tilemap.heightInPixels);
// プレイヤーにカメラを追跡させる
this.cameras.main.startFollow(this.player, true, 1, 1, 0, 300);
Discussion