关于 tilemaps
Phaser 瓦片地图技能参考,介绍地图加载、渲染、查询和修改,适用于使用 Tiled 数据、CSV 或数组的游戏开发者,并涵盖多种地图方向及 CPU、GPU 图层。
主要功能
- 01Tiled JSON、CSV 和数组地图数据
- 02瓦片集与可渲染图层
- 03瓦片碰撞与物理设置
- 04瓦片查询与运行时编辑
- 05GPU 图层渲染
适用场景
- 01基于 Tiled 地图构建关卡
- 02依据瓦片属性设置碰撞
- 03在游戏运行时编辑地图瓦片
- 04根据世界坐标查询瓦片
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ phaserjs/ phaser --skill tilemaps安装后在 Claude Code 中输入/tilemaps确认已加载。
详细介绍
瓦片地图
Phaser 瓦片地图可从 Tiled JSON、CSV 或原始 2D 数组渲染基于瓦片的关卡。
Tilemap保存解析后的地图数据,并提供添加图块集、创建图层、设置碰撞和查询瓦片的方法。图层(TilemapLayer或TilemapGPULayer)是实际渲染瓦片的游戏对象。Phaser 支持正交、等距、六边形和交错地图。
关键源码路径: src/tilemaps/Tilemap.js、src/tilemaps/TilemapLayer.js、src/tilemaps/TilemapGPULayer.js、src/tilemaps/TilemapLayerBase.js、src/tilemaps/Tile.js、src/tilemaps/Tileset.js、src/tilemaps/TilemapFactory.js、src/tilemaps/components/、src/tilemaps/parsers/tiled/
相关技能: ../loading-assets/SKILL.md、../sprites-and-images/SKILL.md
快速开始
class GameScene extends Phaser.Scene {
preload() {
// Load the Tiled JSON and the tileset image
this.load.tilemapTiledJSON('map', 'assets/level1.json');
this.load.image('tiles', 'assets/tilesheet.png');
}
create() {
// Create the tilemap from cached JSON
const map = this.add.tilemap('map');
// Link the tileset image to the tileset name used in Tiled
const tileset = map.addTilesetImage('tilesheet', 'tiles');
// Create a layer - layerID must match the layer name in Tiled
const ground = map.createLayer('Ground', tileset);
// Enable collision on specific tile indexes
ground.setCollision([1, 2, 3]);
}
}
流程始终是:加载 JSON + 图像,创建瓦片地图,添加图块集图像,创建一个或多个图层,设置碰撞。
核心概念
Tilemap 与图层
Tilemap 是数据容器,不是显示对象。它存储解析后的地图数据(图层、图块集、对象),并提供操作这些数据的方法。TilemapLayer 或 TilemapGPULayer 是添加到显示列表、实际执行渲染的游戏对象。
const map = this.add.tilemap('map'); // Data container (not rendered)
const layer = map.createLayer('Ground', tileset); // Game Object (rendered)
Tilemap(key) 会在缓存中查找瓦片地图数据。Tilemap 实例会被缓存。
Tileset(name, firstgid, tileWidth, tileHeight, tileMargin, tileSpacing) 将图块集名称与其在瓦片地图中的第一个 gid 关联起来。它存储 firstgid 和瓦片尺寸、边距、间距。
// tilesetName: the name in Tiled's tileset panel
// key: the Phaser texture key (defaults to tilesetName if omitted)
const tileset = map.addTilesetImage('tilesetName', 'textureKey');
// Override tile dimensions, margin, and spacing if needed
const tileset = map.addTilesetImage('name', 'key', 16, 16, 1, 2);
addTilesetImage(tilesetName, key, tileWidth, tileHeight, tileMargin, tileSpacing, gid) - 如果图块集名称已存在于解析后的地图数据中,则使用它。如果没有(非 Tiled 地图),则创建一个新的图块集。
重要: Phaser 的 Tiled 解析器不支持 "Collection of Images" 图块集。所有瓦片都必须位于单个图块集图像中。
瓦片
图层中的每个单元格都是一个 Tile 对象(src/tilemaps/Tile.js)。关键属性:
index- 图块集中的瓦片索引(空为 -1)x、y- 瓦片坐标(以瓦片为单位,不是像素)pixelX、pixelY- 相对于图层原点的像素位置width、height- 瓦片大小(像素)properties- 来自 Tiled 的自定义属性(对象)collideLeft、collideRight、collideUp、collideDown- 各边缘碰撞标志faceLeft、faceRight、faceTop、faceBottom- 用于碰撞优化的 interesting face 标志collisionCallback- 每个瓦片的碰撞回调函数tint- 色调颜色值(默认0xffffff)tintMode- 色调混合模式(默认TintModes.MULTIPLY)rotation- 旋转角度physics- 用于物理引擎特定数据的对象(例如刚体)alpha、visible、flipX、flipY- 继承自 mixin
TilemapGPULayer(v4.0.0)
TilemapGPULayer 是 TilemapLayer 的高性能、仅 WebGL 替代方案。它使用着色器将整个图层渲染为单个四边形,几乎完全由 GPU 处理。
// Pass gpu: true as the 5th argument to createLayer
const layer = map.createLayer('Ground', tileset, 0, 0, true);
能力:
- 每层仅支持单个图块集(不支持多图块集)
- 最大瓦片地图尺寸:4096x4096 瓦片
- 最大唯一瓦片 ID 数:2^23(8,388,608)
- 支持瓦片翻转和瓦片动画
- 仅支持正交地图(不支持等距/六边形/交错)
- 使用 LINEAR 过滤平滑瓦片边界(无缝)
- 使用 NEAREST 过滤保持像素锐利
限制:
- 图层编辑不会自动显示。修改瓦片后需要调用
generateLayerDataTexture()。 - 仅支持 WebGL 渲染器(无 Canvas 回退)
- 无法在单个图层上使用多个图块集
// If you edit tiles on a GPU layer, regenerate the data texture:
gpuLayer.putTileAt(5, 10, 10);
gpuLayer.generateLayerDataTexture();
TilemapLayerBase
TilemapLayer 和 TilemapGPULayer 都继承自 TilemapLayerBase(src/tilemaps/TilemapLayerBase.js),后者继承自 GameObject。基类提供所有瓦片查询、操作与碰撞方法。它包含以下组件混入:Alpha、BlendMode、ComputedSize、Depth、ElapseTimer、Flip、GetBounds、Lighting、Mask、Origin、RenderNodes、Transform、Visible、ScrollFactor 和 Arcade Physics Collision。
常见模式
从 Tiled JSON 创建
preload() {
this.load.tilemapTiledJSON('map', 'assets/map.json');
this.load.image('tiles', 'assets/tileset.png');
}
create() {
const map = this.add.tilemap('map');
const tileset = map.addTilesetImage('TilesetNameInTiled', 'tiles');
const layer = map.createLayer('LayerNameInTiled', tileset);
}
传递给 createLayer 的 layerID 必须与 Tiled 中的图层名称完全一致。组图层子项会按 'ParentGroup/Layer' 命名约定展平。
多个图层
const map = this.add.tilemap('map');
const tileset = map.addTilesetImage('terrain', 'terrain-img');
const background = map.createLayer('Background', tileset);
const ground = map.createLayer('Ground', tileset);
const foreground = map.createLayer('Foreground', tileset);
// Layers are rendered in creation order. Use depth for finer control:
foreground.setDepth(10);
一个图层可以使用多个瓦片集(仅限 CPU 图层):
const tiles1 = map.addTilesetImage('terrain', 'terrain-img');
const tiles2 = map.addTilesetImage('objects', 'objects-img');
const layer = map.createLayer('Ground', [tiles1, tiles2]);
创建空白图层
const map = this.add.tilemap('map');
const tileset = map.addTilesetImage('terrain', 'terrain-img');
// createBlankLayer(name, tileset, x, y, width, height, tileWidth, tileHeight)
const layer = map.createBlankLayer('dynamic', tileset, 0, 0, 50, 50, 32, 32);
// Fill it with tiles
layer.fill(1); // Fill entire layer with tile index 1
layer.putTileAt(5, 10, 10); // Place tile index 5 at tile coord (10, 10)
碰撞设置
有几种方式可以为 Arcade Physics 启用瓦片碰撞:
// By specific tile indexes
layer.setCollision([1, 2, 3]);
// By range (inclusive)
layer.setCollisionBetween(1, 50);
// By tile property (set in Tiled's tileset editor)
layer.setCollisionByProperty({ collides: true });
// Supports arrays: { type: ['stone', 'lava'] }
// By exclusion - collide on ALL tiles except these
layer.setCollisionByExclusion([-1, 0]); // -1 is empty, 0 is often background
// From Tiled collision editor shapes
layer.setCollisionFromCollisionGroup();
TilemapLayerBase 上的所有碰撞方法都镜像了 Tilemap 上的方法,但不需要 layer 参数。在 Tilemap 上,你可以传入图层引用或使用“当前图层”:
map.setLayer('Ground');
map.setCollision([1, 2, 3]); // Applies to current layer
// Or specify a layer explicitly:
map.setCollision([1, 2, 3], true, true, 'Ground');
物理集成(Arcade)
// Enable collisions between a sprite and a tilemap layer
this.physics.add.collider(player, groundLayer);
// With a callback
this.physics.add.collider(player, groundLayer, (sprite, tile) => {
if (tile.index === 5) {
// Hit a special tile
}
});
// Overlap detection instead of collision
this.physics.add.overlap(player, groundLayer, (sprite, tile) => {
// Player is overlapping this tile
});
图层必须在其瓦片上设置碰撞(通过 setCollision* 方法),物理系统才能检测到它们。图层本身具有 collisionCategory 和 collisionMask 属性,用于碰撞过滤。
瓦片属性
瓦片可以在 Tiled 的瓦片集编辑器中设置自定义属性:
// Access tile properties
const tile = layer.getTileAt(10, 5);
console.log(tile.properties.damage); // Custom property from Tiled
console.log(tile.properties.type); // Custom property from Tiled
// Set collision based on custom properties
layer.setCollisionByProperty({ collides: true });
layer.setCollisionByProperty({ type: ['wall', 'rock'] });
瓦片回调
// Callback by tile index - fires when physics body overlaps these tiles
map.setTileIndexCallback([5, 6, 7], (sprite, tile) => {
// Called for tiles with index 5, 6, or 7
console.log('Hit tile', tile.index, 'at', tile.x, tile.y);
}, this);
// Callback by tile location - fires for tiles in a rectangular area
map.setTileLocationCallback(10, 10, 5, 5, (sprite, tile) => {
// Called for any tile in the 5x5 region starting at (10, 10)
}, this);
// Per-tile callback
const tile = layer.getTileAt(10, 5);
tile.collisionCallback = (sprite, tile) => {
// Custom logic for this specific tile
};
瓦片回调需要刚体与图层之间存在活动的物理碰撞器/重叠。
查询瓦片
const tile = layer.getTileAt(10, 5); // By tile coords (or null)
const tile = layer.getTileAt(10, 5, true); // nonNull: Tile with index -1 instead of null
const tile = layer.getTileAtWorldXY(worldX, worldY); // By world coords
const exists = layer.hasTileAt(10, 5); // Boolean check
// Region queries
const tiles = layer.getTilesWithin(0, 0, 10, 10); // Tile coord region
const tiles = layer.getTilesWithinWorldXY(x, y, w, h); // World coord region
const tiles = layer.getTilesWithinShape(circle); // Shape overlap
// Functional queries
const water = layer.filterTiles(t => t.properties.type === 'water');
const spawn = layer.findTile(t => t.properties.isSpawn);
layer.forEachTile(t => { /* iterate all tiles */ });
在运行时修改瓦片
layer.putTileAt(5, 10, 10); // Place tile index 5 at (10, 10)
layer.putTileAtWorldXY(5, worldX, worldY); // Place by world coords
layer.putTilesAt([[1, 2], [3, 4]], 10, 10); // Place a 2x2 grid
layer.removeTileAt(10, 10); // Remove tile
layer.fill(1, 0, 0, 10, 10); // Fill 10x10 region with index 1
layer.replaceByIndex(5, 10); // Replace all index-5 with index-10
layer.copy(0, 0, 5, 5, 20, 20); // Copy 5x5 from (0,0) to (20,20)
layer.randomize(0, 0, 10, 10, [1, 2, 3, 4]); // Random tiles in region
layer.weightedRandomize([{ index: 1, weight: 4 }, { index: 2, weight: 1 }], 0, 0, 10, 10);
layer.shuffle(0, 0, 10, 10); // Shuffle tiles in region
坐标转换
const tileXY = layer.worldToTileXY(worldX, worldY); // World -> tile coords
const worldXY = layer.tileToWorldXY(tileX, tileY); // Tile -> world coords
// Reuse a vector to avoid allocation
const vec = new Phaser.Math.Vector2();
layer.worldToTileXY(worldX, worldY, true, vec); // snapToFloor = true
对象图层(Tiled)
Tiled 对象图层定义点、矩形和精灵放置。使用 Tilemap 上的 createFromObjects:
// Create sprites from all objects on the 'Enemies' object layer
const enemies = map.createFromObjects('Enemies', {
gid: 26, // Match by tile GID
classType: Enemy // Custom class extending Sprite
});
// Match by name
const coins = map.createFromObjects('Items', {
name: 'coin',
key: 'coin-texture',
frame: 0
});
// Match by type
const spawns = map.createFromObjects('Spawns', {
type: 'player-spawn'
});
// Access raw object layer data
const objectLayer = map.getObjectLayer('Enemies');
objectLayer.objects.forEach(obj => {
console.log(obj.name, obj.x, obj.y, obj.properties);
});
createFromObjects(layerName, config, useTileset) 配置选项:id、gid、name、type、classType(默认 Sprite)、scene、container、key、frame、ignoreTileset。
动画瓦片
瓦片动画在 Tiled 的瓦片集编辑器中定义并自动解析。TilemapLayer 和 TilemapGPULayer 都支持动画瓦片。TilemapLayerBase 使用 ElapseTimer 通过 preUpdate 跟踪动画时间。
等距、六边形和交错地图
// Isometric map
const map = this.add.tilemap('iso-map');
const tileset = map.addTilesetImage('iso-tiles', 'iso-img');
const layer = map.createLayer('Ground', tileset);
// Get tile at world coords in isometric space
const tile = layer.getIsoTileAtWorldXY(worldX, worldY);
// TilemapGPULayer does NOT support iso/hex/staggered - use TilemapLayer
地图的 orientation 属性从 Tiled 数据设置。坐标转换函数会根据方向自动选择。
API 快速参考
Tilemap(数据容器 - 不渲染)
| 方法 | 描述 |
|---|---|
addTilesetImage(name, key, tw, th, margin, spacing, gid, offset) | 将瓦片集名称链接到纹理 |
createLayer(layerID, tileset, x, y, gpu) | 创建图层(gpu=true 用于 GPU 图层) |
createBlankLayer(name, tileset, x, y, w, h, tw, th) | 为程序化地图创建空图层 |
createFromObjects(layerName, config, useTileset) | 将 Tiled 对象转换为 Sprite |
getObjectLayer(name) | 获取原始对象图层数据 |
setLayer(layer) | 为简写方法设置当前活动图层 |
大多数瓦片查询/碰撞/操作方法同时存在于 Tilemap(带额外 layer 参数)和 TilemapLayerBase(不带)上。优先直接在图层上调用。
TilemapLayerBase(渲染图层 - CPU 和 GPU)
碰撞:
setCollision(indexes)、setCollisionBetween(start, stop)、setCollisionByProperty(props)、setCollisionByExclusion(indexes)、setCollisionFromCollisionGroup()、setTileIndexCallback(indexes, cb, ctx)、setTileLocationCallback(x, y, w, h, cb, ctx)
瓦片查询:
getTileAt(x, y, nonNull)、getTileAtWorldXY(wx, wy, nonNull, cam)、getTilesWithin(x, y, w, h, opts)、getTilesWithinWorldXY(wx, wy, w, h, opts, cam)、getTilesWithinShape(shape, opts, cam)、hasTileAt(x, y)、hasTileAtWorldXY(wx, wy, cam)、filterTiles(cb)、findTile(cb)、forEachTile(cb)
瓦片操作:
putTileAt(tile, x, y)、putTileAtWorldXY(tile, wx, wy)、putTilesAt(arr, x, y)、removeTileAt(x, y)、fill(index, x, y, w, h)、copy(sx, sy, w, h, dx, dy)、randomize(x, y, w, h, indexes)、weightedRandomize(weights, x, y, w, h)、shuffle(x, y, w, h)、swapByIndex(a, b)、replaceByIndex(find, replace)、createFromTiles(indexes, replacements, config)
坐标:
worldToTileXY(wx, wy, snap, vec, cam)、tileToWorldXY(tx, ty, vec, cam)
TilemapGPULayer(补充)
| 方法 | 描述 |
|---|---|
generateLayerDataTexture() | 在瓦片编辑后重新生成 GPU 纹理 |
瓦片属性
index(number,-1=空)、x/y(瓦片坐标)、pixelX/pixelY(相对于图层的像素位置)、width/height、properties(来自 Tiled 的对象)、collideLeft/Right/Up/Down(boolean)、collisionCallback(function)、tint(number)、rotation(number)、alpha、flipX/flipY、physics(用于引擎数据的对象)
注意事项
-
Tileset 名称必须与 Tiled 完全一致。
addTilesetImage的第一个参数是 Tiled 中定义的 tileset 名称,而不是 Phaser 纹理键。如果它们不匹配,你会得到null并在控制台看到警告。 -
图层名称必须与 Tiled 完全一致。
createLayer接受 Tiled 中的图层名称(或图层索引)。组图层的子图层会加上'GroupName/LayerName'前缀。 -
每个图层只能创建一次。 用相同的图层 ID 调用
createLayer两次会返回null并给出警告。图层数据只能与一个图层 GameObject 关联。 -
必须先调用
setCollision,物理碰撞器才能生效。 如果不将瓦片标记为可碰撞,this.physics.add.collider()会直接穿过所有瓦片。 -
TilemapGPULayer 仅支持正交投影。 它不支持等距、六边形或交错地图。它每个图层也只支持单个 tileset。
-
TilemapGPULayer 需要手动重新生成纹理。 调用
putTileAt或其他编辑方法后,需调用generateLayerDataTexture(),否则更改不会显示。 -
不支持 "Collection of Images" tileset。 Tiled 解析器要求 tileset 中的所有瓦片都位于单张图片中。导出的 JSON 中必须包含内嵌的 tileset。
-
瓦片索引 -1 表示空。 许多方法默认对空瓦片返回
null。传入nonNull: true可获得一个index === -1的 Tile 对象。 -
tilemap 工厂中的
insertNull。 创建 tilemap 时,insertNull: true会为空瓦片存储null,而不是 index 为 -1 的 Tile 对象。这可为大型稀疏地图节省内存,但会阻止在空单元格中动态放置瓦片。 -
瓦片回调仅在物理激活时触发。
setTileIndexCallback和setTileLocationCallback需要物理碰撞器或刚体与图层之间的重叠才能触发。 -
图层位置与 Tiled 偏移。 如果在
createLayer中未指定x和y,它们会默认为 Tiled 中定义的图层偏移,而不是 (0, 0)。
源文件
| 文件 | 用途 |
|---|---|
src/tilemaps/Tilemap.js | 主数据容器,包含所有地图级方法 |
src/tilemaps/TilemapFactory.js | 在 GameObjectFactory 上注册 this.add.tilemap() |
src/tilemaps/TilemapLayerBase.js | CPU 与 GPU 图层共用的基类(继承自 GameObject) |
src/tilemaps/TilemapLayer.js | CPU 渲染图层(支持多图块集、所有图块集) |
src/tilemaps/TilemapGPULayer.js | GPU 加速图层(v4、WebGL、正交投影、单一图块集) |
src/tilemaps/Tile.js | 单个图块数据(索引、位置、碰撞、属性) |
src/tilemaps/Tileset.js | 图块集数据(名称、firstgid、尺寸、图像) |
src/tilemaps/components/ | 纯函数:SetCollision、GetTileAt、PutTileAt、SetTileIndexCallback |
src/tilemaps/parsers/tiled/ | Tiled JSON 解析器:ParseJSONTiled、ParseTileLayers、ParseObjectLayers、ParseTilesets、BuildTilesetIndex |
src/tilemaps/ParseToTilemap.js | 协调解析与 Tilemap 创建 |
src/tilemaps/mapdata/Layer.js | 图层数据结构 |