phaser-tilemap

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Phaser 4 Tilemaps

Phaser 4 Tilemaps

Phaser 4 has first-class support for Tiled map files (JSON format). The recommended workflow is: design in Tiled → export JSON → load in Phaser → create layers → set up collision.
Phaser 4 对Tiled地图文件(JSON格式)提供一流支持。推荐工作流为:在Tiled中设计 → 导出JSON → 在Phaser中加载 → 创建图层 → 设置碰撞。

Tiled Editor Workflow

Tiled编辑器工作流

Download Tiled for free at mapeditor.org.
可在 mapeditor.org 免费下载Tiled。

Creating a New Map

创建新地图

  1. File → New → New Map
  2. Set Orientation to Orthogonal (most 2D games) or Isometric (top-down RPG/strategy)
  3. Set Tile layer format to CSV or Base64 — both work with Phaser
  4. Set Tile size to match your tileset (common: 16x16, 32x32)
  5. Set Map size in tiles (e.g. 40 wide × 23 tall for a 1280x736 map at 32px tiles)
  1. 文件 → 新建 → 新建地图
  2. 方向设置为正交(适用于大多数2D游戏)或等轴测(适用于俯视角RPG/策略游戏)
  3. 瓦片图层格式设置为CSV或Base64——两者均兼容Phaser
  4. 瓦片尺寸设置为与你的瓦片集匹配(常见尺寸:16x16、32x32)
  5. 设置地图的瓦片数量(例如,使用32px瓦片时,40宽×23高对应1280x736的地图)

Adding a Tileset

添加瓦片集

  1. In the Tilesets panel (bottom right), click the + button → New Tileset
  2. Set Name — this exact name is used in
    map.addTilesetImage()
    as the first argument
  3. Set Type to Based on Tileset Image
  4. Browse to your PNG, set tile width/height to match
  5. Margin and spacing: set these if your tileset has padding between tiles (often 0)
  1. 在瓦片集面板(右下角),点击**+**按钮 → 新建瓦片集
  2. 设置名称——此名称需与
    map.addTilesetImage()
    的第一个参数完全一致
  3. 类型设置为基于瓦片集图像
  4. 浏览选择你的PNG文件,设置瓦片宽/高以匹配实际尺寸
  5. 边距与间距:如果你的瓦片集在瓦片之间有空白填充,请设置对应数值(通常为0)

Marking Collision Tiles

标记碰撞瓦片

  1. Select your tileset in the Tilesets panel
  2. Click the wrench icon (Edit Tileset) to open the tileset editor
  3. Select the tiles that should collide (click to select, Ctrl+click for multiple)
  4. In the Properties panel (left side), click the + button to add a property
  5. Set Name to
    collides
    , Type to
    bool
    , Value to
    true
  6. Close the tileset editor
In Phaser, call
layer.setCollisionByProperty({ collides: true })
to activate these tiles.
  1. 在瓦片集面板中选择你的瓦片集
  2. 点击扳手图标(编辑瓦片集)打开瓦片集编辑器
  3. 选择需要设置碰撞的瓦片(单击选择,Ctrl+单击多选)
  4. 在属性面板(左侧),点击**+**按钮添加属性
  5. 名称设为
    collides
    类型设为
    bool
    设为
    true
  6. 关闭瓦片集编辑器
在Phaser中,调用
layer.setCollisionByProperty({ collides: true })
即可激活这些瓦片的碰撞功能。

Layer Naming Conventions

图层命名规范

Use consistent layer names — Phaser references them by string:
Layer NameTypePurpose
Background
Tile LayerSky, distant scenery — no collision
Ground
Tile LayerMain walkable surface — collision enabled
Hazards
Tile LayerSpikes, lava — overlap (not collide)
Foreground
Tile LayerTrees, arches that render in front of player
Objects
Object LayerSpawn points, triggers, enemies
使用统一的图层名称——Phaser通过字符串引用图层:
图层名称类型用途
Background
Tile Layer天空、远景——无碰撞
Ground
Tile Layer主要行走地面——启用碰撞
Hazards
Tile Layer尖刺、熔岩——重叠检测(非碰撞)
Foreground
Tile Layer树木、拱门等渲染在玩家前方的元素
Objects
Object Layer生成点、触发器、敌人

Object Layer Usage

对象图层用法

Object Layers in Tiled let you place named points, rectangles, and polygons that Phaser can query.
Named objects (for unique things like player spawn):
  1. Add Object Layer named
    Objects
  2. Select the Rectangle tool, place an object on the map
  3. In Properties, set Name to
    PlayerSpawn
Typed objects (for groups of the same kind, like enemies):
  1. Place objects and set Type (Tiled 1.8: use Class) to
    Enemy
  2. Add custom properties: click +, add
    health
    (int, 100),
    patrol
    (bool, true)
Tiled中的对象图层可用于放置命名点、矩形和多边形,供Phaser查询使用。
命名对象(用于玩家生成点等唯一元素):
  1. 添加名为
    Objects
    的对象图层
  2. 选择矩形工具,在地图上放置一个对象
  3. 在属性中,将名称设为
    PlayerSpawn
类型化对象(用于敌人等同类元素组):
  1. 放置对象并将类型(Tiled 1.8+版本使用)设为
    Enemy
  2. 添加自定义属性:点击**+**,添加
    health
    (整数,100)、
    patrol
    (布尔值,true)

Export Settings

导出设置

  1. File → Export As → JSON Map Files (
    .json
    )
  2. In export options, enable Embed tilesets — this avoids external
    .tsx
    dependencies
  3. Save to
    public/assets/tilemaps/level1.json
  4. Place the tileset PNG at
    public/assets/images/terrain.png

  1. 文件 → 导出为 → JSON地图文件(
    .json
  2. 在导出选项中,启用嵌入瓦片集——这将避免依赖外部
    .tsx
    文件
  3. 保存至
    public/assets/tilemaps/level1.json
  4. 将瓦片集PNG放置在
    public/assets/images/terrain.png

Loading Assets

加载资源

typescript
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');
}

typescript
preload(): void {
  // 键名必须与map.addTilesetImage()的第一个参数匹配
  this.load.tilemapTiledJSON('level1', 'assets/tilemaps/level1.json');

  // 键名必须与map.addTilesetImage()的第二个参数匹配
  this.load.image('terrain', 'assets/images/terrain.png');
}

Creating the Map and Layers

创建地图与图层

typescript
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
}
Common reason tilemap does not show: the tileset name in
addTilesetImage
does not exactly match the name set in Tiled. Open the JSON file and check the
"name"
field inside
"tilesets"
.

typescript
private map!: Phaser.Tilemaps.Tilemap;
private groundLayer!: Phaser.Tilemaps.TilemapLayer;

create(): void {
  this.map = this.make.tilemap({ key: 'level1' });

  // 第一个参数:Tiled中设置的瓦片集名称(必须完全匹配,区分大小写)
  // 第二个参数:this.load.image()的键名
  const tileset = this.map.addTilesetImage('terrain', 'terrain');

  // 装饰性背景——无碰撞
  const bgLayer = this.map.createLayer('Background', tileset!, 0, 0);

  // 主地面图层——下方将启用碰撞
  this.groundLayer = this.map.createLayer('Ground', tileset!, 0, 0)!;

  // 前景图层渲染在玩家上方
  const fgLayer = this.map.createLayer('Foreground', tileset!, 0, 0);
  fgLayer!.setDepth(10);  // 玩家深度应设为1–9
}
tilemap不显示的常见原因
addTilesetImage
中的瓦片集名称与Tiled中设置的名称不完全匹配。打开JSON文件并检查
"tilesets"
内的
"name"
字段。

Collision

碰撞设置

By Property (recommended)

通过属性设置(推荐)

Uses the
collides: true
property set in Tiled's tileset editor:
typescript
this.groundLayer.setCollisionByProperty({ collides: true });
this.physics.add.collider(this.player, this.groundLayer);
使用在Tiled瓦片集编辑器中设置的
collides: true
属性:
typescript
this.groundLayer.setCollisionByProperty({ collides: true });
this.physics.add.collider(this.player, this.groundLayer);

By Tile Index Range

通过瓦片索引范围设置

Collide tiles with GID (global ID) 1 through 10:
typescript
this.groundLayer.setCollisionBetween(1, 10);
为全局ID(GID)1至10的瓦片设置碰撞:
typescript
this.groundLayer.setCollisionBetween(1, 10);

By Exclusion

通过排除法设置

Collide all tiles except empty (-1) and a specific index:
typescript
this.groundLayer.setCollisionByExclusion([-1, 0]);
为除空瓦片(-1)和特定索引外的所有瓦片设置碰撞:
typescript
this.groundLayer.setCollisionByExclusion([-1, 0]);

Debug Rendering

调试渲染

Visualize collision tiles during development:
typescript
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
});

在开发过程中可视化碰撞瓦片:
typescript
const debugGraphics = this.add.graphics();
this.groundLayer.renderDebug(debugGraphics, {
  tileColor: null,                                       // 非碰撞瓦片
  collidingTileColor: new Phaser.Display.Color(243, 134, 48, 128),  // 橙色
  faceColor: new Phaser.Display.Color(40, 39, 37, 255), // 边缘轮廓
});

Multiple Layers

多图层设置

typescript
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);
}

typescript
create(): void {
  const tileset = this.map.addTilesetImage('terrain', 'terrain')!;

  const bgLayer    = this.map.createLayer('Background', tileset, 0, 0);  // 深度0(默认)
  const groundLayer = this.map.createLayer('Ground', tileset, 0, 0)!;    // 深度0(默认)
  const fgLayer    = this.map.createLayer('Foreground', tileset, 0, 0);

  fgLayer!.setDepth(10);          // 渲染在玩家上方

  groundLayer.setCollisionByProperty({ collides: true });

  // 玩家和敌人的深度应设为1至9
  this.player.setDepth(5);
}

Object Layers

对象图层读取

Read spawn points, triggers, and entity placements from Tiled's Object Layer:
typescript
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]));
}

从Tiled的对象图层读取生成点、触发器和实体位置:
typescript
create(): void {
  // 查找单个命名对象——返回第一个匹配项
  const spawnPoint = this.map.findObject('Objects', obj => obj.name === 'PlayerSpawn');
  this.player = this.physics.add.sprite(spawnPoint!.x!, spawnPoint!.y!, 'player');

  // 获取指定类型的所有对象(Tiled 1.8+版本使用“类”字段)
  const enemyObjects = this.map.filterObjects('Objects', obj => obj.type === 'Enemy');
  enemyObjects?.forEach(obj => {
    // 以数组形式访问自定义属性:obj.properties
    const props = this.parseProperties(obj.properties);
    this.spawnEnemy(obj.x!, obj.y!, props.health ?? 100);
  });

  // 查找触发器区域(在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(`进入触发器:${obj.name}`);
    });
  });
}

// 辅助函数:将Tiled属性数组转换为普通对象
private parseProperties(props?: { name: string; value: unknown }[]): Record<string, unknown> {
  if (!props) return {};
  return Object.fromEntries(props.map(p => [p.name, p.value]));
}

Camera and World Bounds

相机与世界边界

Always set world bounds to the map size so the player cannot walk out of the map:
typescript
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
}

务必将世界边界设置为地图尺寸,防止玩家走出地图:
typescript
create(): void {
  // 约束物理体范围
  this.physics.world.setBounds(0, 0, this.map.widthInPixels, this.map.heightInPixels);

  // 约束相机范围
  this.cameras.main.setBounds(0, 0, this.map.widthInPixels, this.map.heightInPixels);

  // 跟随玩家
  this.cameras.main.startFollow(this.player, true, 0.1, 0.1);  // x/y插值为0.1实现平滑跟随
}

Dynamic Tile Manipulation

动态瓦片操作

Modify tiles at runtime (destructible terrain, switches, etc.):
typescript
// 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)!;

在运行时修改瓦片(可破坏地形、开关等):
typescript
// 读取世界坐标处的瓦片
const tile = this.groundLayer.getTileAtWorldXY(ptr.worldX, ptr.worldY);
if (tile) {
  console.log(`瓦片索引:${tile.index}`);
}

// 在世界坐标处放置瓦片
this.groundLayer.putTileAtWorldXY(5, ptr.worldX, ptr.worldY);

// 移除瓦片(将索引设为-1/空)
this.groundLayer.removeTileAtWorldXY(ptr.worldX, ptr.worldY);

// 转换世界坐标与瓦片坐标
const tileXY = this.groundLayer.worldToTileXY(worldX, worldY)!;
const worldXY = this.groundLayer.tileToWorldXY(tileXY.x, tileXY.y)!;

Parallax Layers

视差图层

Scroll a layer at a different speed than the camera to create depth:
typescript
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)
}

让图层以与相机不同的速度滚动,营造深度感:
typescript
create(): void {
  const cloudLayer = this.map.createLayer('Clouds', tileset!, 0, 0);
  cloudLayer!.setScrollFactor(0.2);  // 以相机20%的速度移动(最远处)

  const hillLayer = this.map.createLayer('Hills', tileset!, 0, 0);
  hillLayer!.setScrollFactor(0.5);   // 以相机50%的速度移动

  // 地面图层
  const groundLayer = this.map.createLayer('Ground', tileset!, 0, 0);
  groundLayer!.setScrollFactor(1);   // 以相机100%的速度移动(默认)
}

Multiple Tilesets

多瓦片集使用

A single layer can use tiles from multiple tilesets:
typescript
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);
}

单个图层可使用来自多个瓦片集的瓦片:
typescript
create(): void {
  const tiles1 = this.map.addTilesetImage('tileset-a', 'tiles-a');
  const tiles2 = this.map.addTilesetImage('tileset-b', 'tiles-b');

  // 传入瓦片集数组——Phaser会自动解析瓦片GID
  const layer = this.map.createLayer('Ground', [tiles1!, tiles2!], 0, 0);
}

Additional Resources

额外资源

Reference Files

参考文件

  • references/tilemap-api.md
    — Complete API reference for Tilemap, TilemapLayer, Tileset, and MapData
  • references/tiled-workflow.md
    — Detailed step-by-step Tiled Editor setup guide with common mistakes
  • references/tilemap-api.md
    —— Tilemap、TilemapLayer、Tileset和MapData的完整API参考
  • references/tiled-workflow.md
    —— 详细的Tiled编辑器分步设置指南及常见错误说明