phaser-tilemap について
phaser-tilemap は、Tiled の JSON マップを Phaser 4 で扱うためのスキルです。マップ作成からレイヤーやオブジェクト、衝突設定、実行中のタイル操作までを説明します。
主な機能
- 01Tiled JSON マップのワークフロー
- 02タイルレイヤーとオブジェクトの設定
- 03プロパティやインデックスによる衝突設定
- 04カメラと物理ワールド境界の設定
- 05実行中のタイル編集と視差表現
活用シーン
- 01Phaser 4 用 Tiled マップの作成
- 02地面や危険レイヤーの設定
- 03スポーン地点や敵オブジェクトの読み込み
- 04タイル衝突のデバッグ
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yakoub-ai/ phaser4-gamedev --skill phaser-tilemapインストール後、Claude Code で/phaser-tilemapを入力して読み込まれたか確認します。
同梱ファイルreferences/tiled-workflow.mdreferences/tilemap-api.md
詳細
Phaser 4 タイルマップ
Phaser 4 は Tiled マップファイル(JSON 形式)をファーストクラスでサポートしています。推奨されるワークフローは次のとおりです: Tiled で設計 → JSON をエクスポート → Phaser で読み込み → レイヤーを作成 → 衝突を設定。
Tiled エディターのワークフロー
Tiled は mapeditor.org から無料でダウンロードできます。
新しいマップの作成
- File → New → New Map
- Orientation を Orthogonal(ほとんどの 2D ゲーム)または Isometric(トップダウン RPG/ストラテジー)に設定
- Tile layer format を CSV または Base64 に設定 — どちらも Phaser で動作します
- Tile size をタイルセットに合わせて設定(一般的には 16x16、32x32)
- Map size をタイル単位で設定(例: 32px タイルで 1280x736 のマップなら横 40 × 縦 23)
タイルセットの追加
- Tilesets パネル(右下)で + ボタンをクリック → New Tileset
- Name を設定 — この名前がそのまま
map.addTilesetImage()の第 1 引数として使用されます - Type を Based on Tileset Image に設定
- PNG を選択し、タイルの幅/高さを一致するように設定
- Margin と spacing: タイルセットのタイル間に余白がある場合は設定します(多くの場合は 0)
衝突タイルのマーク
- Tilesets パネルでタイルセットを選択
- レンチアイコン(Edit Tileset)をクリックしてタイルセットエディターを開く
- 衝突させるタイルを選択(クリックで選択、Ctrl+クリックで複数選択)
- Properties パネル(左側)で + ボタンをクリックしてプロパティを追加
- Name を
collides、Type をbool、Value をtrueに設定 - タイルセットエディターを閉じる
Phaser では layer.setCollisionByProperty({ collides: true }) を呼び出してこれらのタイルを有効にします。
レイヤー命名規則
一貫したレイヤー名を使用してください — Phaser は文字列でレイヤーを参照します:
| レイヤー名 | 種類 | 目的 |
|---|---|---|
Background | タイルレイヤー | 空、遠景 — 衝突なし |
Ground | タイルレイヤー | 主な歩行可能な表面 — 衝突有効 |
Hazards | タイルレイヤー | トゲ、溶岩 — オーバーラップ(衝突ではない) |
Foreground | タイルレイヤー | プレイヤーの手前に描画される木、アーチ |
Objects | オブジェクトレイヤー | スポーンポイント、トリガー、敵 |
オブジェクトレイヤーの使用方法
Tiled のオブジェクトレイヤーを使うと、Phaser から問い合わせできる名前付きのポイント、矩形、ポリゴンを配置できます。
名前付きオブジェクト(プレイヤースポーンなどの一意なものに使用):
Objectsという名前のオブジェクトレイヤーを追加- Rectangle ツールを選択し、マップ上にオブジェクトを配置
- Properties で Name を
PlayerSpawnに設定
型付きオブジェクト(敵などの同じ種類のグループに使用):
- オブジェクトを配置し、Type(Tiled 1.8 では Class)を
Enemyに設定 - カスタムプロパティを追加: + をクリックし、
health(int、100)、patrol(bool、true)を追加
エクスポート設定
- File → Export As → JSON Map Files(
.json) - エクスポートオプションで Embed tilesets を有効化 — これにより外部の
.tsx依存を回避できます public/assets/tilemaps/level1.jsonに保存- タイルセットの PNG を
public/assets/images/terrain.pngに配置
アセットの読み込み
preload(): void {
// Key must match first arg of map.addTilesetImage()
this.load.tilemapTiledJSON('level1', 'assets/tilemaps/level1.json');
// Key must match second arg of map.addTilesetImage()
this.load.image('terrain', 'assets/images/terrain.png');
}
マップとレイヤーの作成
private map!: Phaser.Tilemaps.Tilemap;
private groundLayer!: Phaser.Tilemaps.TilemapLayer;
create(): void {
this.map = this.make.tilemap({ key: 'level1' });
// First arg: tileset name as set in Tiled (must match exactly, case-sensitive)
// Second arg: the this.load.image() key
const tileset = this.map.addTilesetImage('terrain', 'terrain');
// Decorative background — no collision
const bgLayer = this.map.createLayer('Background', tileset!, 0, 0);
// Main ground layer — collision enabled below
this.groundLayer = this.map.createLayer('Ground', tileset!, 0, 0)!;
// Foreground renders above the player
const fgLayer = this.map.createLayer('Foreground', tileset!, 0, 0);
fgLayer!.setDepth(10); // player depth should be 1–9
}
タイルマップが表示されないよくある原因: addTilesetImage のタイルセット名が Tiled で設定した名前と完全に一致していないことです。JSON ファイルを開き、"tilesets" 内の "name" フィールドを確認してください。
衝突
プロパティによる方法(推奨)
Tiled のタイルセットエディターで設定した collides: true プロパティを使用します:
this.groundLayer.setCollisionByProperty({ collides: true });
this.physics.add.collider(this.player, this.groundLayer);
タイルインデックス範囲による方法
GID(グローバル ID)1 から 10 のタイルを衝突させます:
this.groundLayer.setCollisionBetween(1, 10);
除外による方法
空(-1)と特定のインデックスを除くすべてのタイルを衝突させます:
this.groundLayer.setCollisionByExclusion([-1, 0]);
デバッグ描画
開発中に衝突タイルを可視化します:
const debugGraphics = this.add.graphics();
this.groundLayer.renderDebug(debugGraphics, {
tileColor: null, // non-colliding tiles
collidingTileColor: new Phaser.Display.Color(243, 134, 48, 128), // orange
faceColor: new Phaser.Display.Color(40, 39, 37, 255), // face outlines
});
複数のレイヤー
create(): void {
const tileset = this.map.addTilesetImage('terrain', 'terrain')!;
const bgLayer = this.map.createLayer('Background', tileset, 0, 0); // depth 0 (default)
const groundLayer = this.map.createLayer('Ground', tileset, 0, 0)!; // depth 0 (default)
const fgLayer = this.map.createLayer('Foreground', tileset, 0, 0);
fgLayer!.setDepth(10); // renders above player
groundLayer.setCollisionByProperty({ collides: true });
// Player and enemies should have depth between 1 and 9
this.player.setDepth(5);
}
オブジェクトレイヤー
Tiled のオブジェクトレイヤーからスポーンポイント、トリガー、エンティティの配置を読み取ります:
create(): void {
// Find a single named object — returns the first match
const spawnPoint = this.map.findObject('Objects', obj => obj.name === 'PlayerSpawn');
this.player = this.physics.add.sprite(spawnPoint!.x!, spawnPoint!.y!, 'player');
// Get all objects of a given type (Tiled "Class" field in 1.8+)
const enemyObjects = this.map.filterObjects('Objects', obj => obj.type === 'Enemy');
enemyObjects?.forEach(obj => {
// Access custom properties as an array: obj.properties
const props = this.parseProperties(obj.properties);
this.spawnEnemy(obj.x!, obj.y!, props.health ?? 100);
});
// Find trigger zones (rectangles placed in Tiled)
const triggers = this.map.filterObjects('Objects', obj => obj.type === 'Trigger');
triggers?.forEach(obj => {
const zone = this.add.zone(obj.x! + obj.width! / 2, obj.y! + obj.height! / 2, obj.width!, obj.height!);
this.physics.world.enable(zone);
this.physics.add.overlap(this.player, zone, () => {
console.log(`Entered trigger: ${obj.name}`);
});
});
}
// Helper: convert Tiled properties array to plain object
private parseProperties(props?: { name: string; value: unknown }[]): Record<string, unknown> {
if (!props) return {};
return Object.fromEntries(props.map(p => [p.name, p.value]));
}
カメラとワールド境界
プレイヤーがマップの外に出られないように、常にワールド境界をマップサイズに設定してください:
create(): void {
// Constrain physics bodies
this.physics.world.setBounds(0, 0, this.map.widthInPixels, this.map.heightInPixels);
// Constrain camera
this.cameras.main.setBounds(0, 0, this.map.widthInPixels, this.map.heightInPixels);
// Follow the player
this.cameras.main.startFollow(this.player, true, 0.1, 0.1); // lerp x/y = 0.1 for smooth follow
}
動的なタイル操作
実行時にタイルを変更します(破壊可能な地形、スイッチなど):
// Read a tile at a world position
const tile = this.groundLayer.getTileAtWorldXY(ptr.worldX, ptr.worldY);
if (tile) {
console.log(`Tile index: ${tile.index}`);
}
// Place a tile at a world position
this.groundLayer.putTileAtWorldXY(5, ptr.worldX, ptr.worldY);
// Remove a tile (sets index to -1 / empty)
this.groundLayer.removeTileAtWorldXY(ptr.worldX, ptr.worldY);
// Convert between world and tile coordinates
const tileXY = this.groundLayer.worldToTileXY(worldX, worldY)!;
const worldXY = this.groundLayer.tileToWorldXY(tileXY.x, tileXY.y)!;
パララックスレイヤー
カメラとは異なる速度でレイヤーをスクロールさせて奥行きを生み出します:
create(): void {
const cloudLayer = this.map.createLayer('Clouds', tileset!, 0, 0);
cloudLayer!.setScrollFactor(0.2); // moves at 20% camera speed (furthest back)
const hillLayer = this.map.createLayer('Hills', tileset!, 0, 0);
hillLayer!.setScrollFactor(0.5); // moves at 50% camera speed
// Ground layer
const groundLayer = this.map.createLayer('Ground', tileset!, 0, 0);
groundLayer!.setScrollFactor(1); // moves at 100% (default)
}
複数のタイルセット
1 つのレイヤーで複数のタイルセットのタイルを使用できます:
create(): void {
const tiles1 = this.map.addTilesetImage('tileset-a', 'tiles-a');
const tiles2 = this.map.addTilesetImage('tileset-b', 'tiles-b');
// Pass an array of tilesets — Phaser resolves tile GIDs automatically
const layer = this.map.createLayer('Ground', [tiles1!, tiles2!], 0, 0);
}
追加リソース
参照ファイル
references/tilemap-api.md— Tilemap、TilemapLayer、Tileset、MapData の完全な API リファレンスreferences/tiled-workflow.md— よくある間違いを交えた、Tiled Editor の詳細なステップバイステップセットアップガイド