phaser-tilemap
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePhaser 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
创建新地图
- File → New → New Map
- Set Orientation to Orthogonal (most 2D games) or Isometric (top-down RPG/strategy)
- Set Tile layer format to CSV or Base64 — both work with Phaser
- Set Tile size to match your tileset (common: 16x16, 32x32)
- Set Map size in tiles (e.g. 40 wide × 23 tall for a 1280x736 map at 32px tiles)
- 文件 → 新建 → 新建地图
- 将方向设置为正交(适用于大多数2D游戏)或等轴测(适用于俯视角RPG/策略游戏)
- 将瓦片图层格式设置为CSV或Base64——两者均兼容Phaser
- 将瓦片尺寸设置为与你的瓦片集匹配(常见尺寸:16x16、32x32)
- 设置地图的瓦片数量(例如,使用32px瓦片时,40宽×23高对应1280x736的地图)
Adding a Tileset
添加瓦片集
- In the Tilesets panel (bottom right), click the + button → New Tileset
- Set Name — this exact name is used in as the first argument
map.addTilesetImage() - Set Type to Based on Tileset Image
- Browse to your PNG, set tile width/height to match
- Margin and spacing: set these if your tileset has padding between tiles (often 0)
- 在瓦片集面板(右下角),点击**+**按钮 → 新建瓦片集
- 设置名称——此名称需与的第一个参数完全一致
map.addTilesetImage() - 将类型设置为基于瓦片集图像
- 浏览选择你的PNG文件,设置瓦片宽/高以匹配实际尺寸
- 边距与间距:如果你的瓦片集在瓦片之间有空白填充,请设置对应数值(通常为0)
Marking Collision Tiles
标记碰撞瓦片
- Select your tileset in the Tilesets panel
- Click the wrench icon (Edit Tileset) to open the tileset editor
- Select the tiles that should collide (click to select, Ctrl+click for multiple)
- In the Properties panel (left side), click the + button to add a property
- Set Name to , Type to
collides, Value tobooltrue - Close the tileset editor
In Phaser, call to activate these tiles.
layer.setCollisionByProperty({ collides: true })- 在瓦片集面板中选择你的瓦片集
- 点击扳手图标(编辑瓦片集)打开瓦片集编辑器
- 选择需要设置碰撞的瓦片(单击选择,Ctrl+单击多选)
- 在属性面板(左侧),点击**+**按钮添加属性
- 将名称设为,类型设为
collides,值设为booltrue - 关闭瓦片集编辑器
在Phaser中,调用即可激活这些瓦片的碰撞功能。
layer.setCollisionByProperty({ collides: true })Layer Naming Conventions
图层命名规范
Use consistent layer names — Phaser references them by string:
| Layer Name | Type | Purpose |
|---|---|---|
| Tile Layer | Sky, distant scenery — no collision |
| Tile Layer | Main walkable surface — collision enabled |
| Tile Layer | Spikes, lava — overlap (not collide) |
| Tile Layer | Trees, arches that render in front of player |
| Object Layer | Spawn points, triggers, enemies |
使用统一的图层名称——Phaser通过字符串引用图层:
| 图层名称 | 类型 | 用途 |
|---|---|---|
| Tile Layer | 天空、远景——无碰撞 |
| Tile Layer | 主要行走地面——启用碰撞 |
| Tile Layer | 尖刺、熔岩——重叠检测(非碰撞) |
| Tile Layer | 树木、拱门等渲染在玩家前方的元素 |
| 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):
- Add Object Layer named
Objects - Select the Rectangle tool, place an object on the map
- In Properties, set Name to
PlayerSpawn
Typed objects (for groups of the same kind, like enemies):
- Place objects and set Type (Tiled 1.8: use Class) to
Enemy - Add custom properties: click +, add (int, 100),
health(bool, true)patrol
Tiled中的对象图层可用于放置命名点、矩形和多边形,供Phaser查询使用。
命名对象(用于玩家生成点等唯一元素):
- 添加名为的对象图层
Objects - 选择矩形工具,在地图上放置一个对象
- 在属性中,将名称设为
PlayerSpawn
类型化对象(用于敌人等同类元素组):
- 放置对象并将类型(Tiled 1.8+版本使用类)设为
Enemy - 添加自定义属性:点击**+**,添加(整数,100)、
health(布尔值,true)patrol
Export Settings
导出设置
- File → Export As → JSON Map Files ()
.json - In export options, enable Embed tilesets — this avoids external dependencies
.tsx - Save to
public/assets/tilemaps/level1.json - Place the tileset PNG at
public/assets/images/terrain.png
- 文件 → 导出为 → JSON地图文件()
.json - 在导出选项中,启用嵌入瓦片集——这将避免依赖外部文件
.tsx - 保存至
public/assets/tilemaps/level1.json - 将瓦片集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 does not exactly match the name set in Tiled. Open the JSON file and check the field inside .
addTilesetImage"name""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不显示的常见原因:中的瓦片集名称与Tiled中设置的名称不完全匹配。打开JSON文件并检查内的字段。
addTilesetImage"tilesets""name"Collision
碰撞设置
By Property (recommended)
通过属性设置(推荐)
Uses the property set in Tiled's tileset editor:
collides: truetypescript
this.groundLayer.setCollisionByProperty({ collides: true });
this.physics.add.collider(this.player, this.groundLayer);使用在Tiled瓦片集编辑器中设置的属性:
collides: truetypescript
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
参考文件
- — Complete API reference for Tilemap, TilemapLayer, Tileset, and MapData
references/tilemap-api.md - — Detailed step-by-step Tiled Editor setup guide with common mistakes
references/tiled-workflow.md
- —— Tilemap、TilemapLayer、Tileset和MapData的完整API参考
references/tilemap-api.md - —— 详细的Tiled编辑器分步设置指南及常见错误说明
references/tiled-workflow.md