| name | tilemaps |
| description | Use this skill when working with tilemaps in Phaser 4. Covers loading Tiled JSON maps, creating tilemap layers, tile collision, dynamic tiles, tile properties, and tilemap camera culling. Triggers on: Tilemap, Tiled, tilemap layer, tile collision, tile properties. |
Tilemaps
Phaser Tilemaps render tile-based levels from Tiled JSON, CSV, or raw 2D arrays. A Tilemap holds parsed map data and provides methods to add tilesets, create layers, set collision, and query tiles. Layers (TilemapLayer or TilemapGPULayer) are the Game Objects that actually render tiles. Phaser supports orthogonal, isometric, hexagonal, and staggered maps.
Key source paths: 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/
Related skills: ../loading-assets/SKILL.md, ../sprites-and-images/SKILL.md
Quick Start
class GameScene extends Phaser.Scene {
preload() {
this.load.tilemapTiledJSON('map', 'assets/level1.json');
this.load.image('tiles', 'assets/tilesheet.png');
}
create() {
const map = this.add.tilemap('map');
const tileset = map.addTilesetImage('tilesheet', 'tiles');
const ground = map.createLayer('Ground', tileset);
ground.setCollision([1, 2, 3]);
}
}
The flow is always: load JSON + image, create tilemap, add tileset image, create layer(s), set collision.
Core Concepts
Tilemap vs Layer
A Tilemap is a data container, not a display object. It stores parsed map data (layers, tilesets, objects) and provides methods that operate on them. A TilemapLayer or TilemapGPULayer is the actual Game Object added to the display list that renders tiles.
const map = this.add.tilemap('map');
const layer = map.createLayer('Ground', tileset);
this.add.tilemap(key) is a factory registered on GameObjectFactory. It delegates to ParseToTilemap which reads from the cache and returns a Tilemap instance.
Tilesets
A Tileset (src/tilemaps/Tileset.js) links a tileset name (from Tiled) to a loaded texture. It stores firstgid, tile dimensions, margin, and spacing.
const tileset = map.addTilesetImage('tilesetName', 'textureKey');
const tileset = map.addTilesetImage('name', 'key', 16, 16, 1, 2);
addTilesetImage(tilesetName, key, tileWidth, tileHeight, tileMargin, tileSpacing, gid, tileOffset) - If the tileset name already exists in the parsed map data, it updates the existing Tileset object with the texture. If not (non-Tiled maps), it creates a new Tileset.
Important: The Phaser Tiled parser does not support "Collection of Images" tilesets. All tiles must be in a single tileset image per tileset.
The Tile Class
Each cell in a layer is a Tile object (src/tilemaps/Tile.js). Key properties:
index - tile index in the tileset (-1 for empty)
x, y - tile coordinates (in tiles, not pixels)
pixelX, pixelY - pixel position relative to layer origin
width, height - tile size in pixels
properties - custom properties from Tiled (object)
collideLeft, collideRight, collideUp, collideDown - per-edge collision flags
faceLeft, faceRight, faceTop, faceBottom - interesting face flags for collision optimization
collisionCallback - per-tile collision callback function
tint - tint color value (default 0xffffff)
tintMode - tint blend mode (default TintModes.MULTIPLY)
rotation - rotation angle
physics - object for physics-engine-specific data (e.g. bodies)
alpha, visible, flipX, flipY - inherited from mixins
TilemapGPULayer (v4.0.0)
TilemapGPULayer is a high-performance WebGL-only alternative to TilemapLayer. It renders the entire layer as a single quad using a shader, making it almost entirely GPU-bound.
const layer = map.createLayer('Ground', tileset, 0, 0, true);
Capabilities:
- Single tileset per layer only (no multi-tileset)
- Max tilemap size: 4096x4096 tiles
- Max unique tile IDs: 2^23 (8,388,608)
- Supports tile flip and tile animation
- Orthographic maps only (no iso/hex/staggered)
- Smooth tile borders with LINEAR filtering (no seams)
- Sharp pixels with NEAREST filtering
Restrictions:
- Layer edits do not display automatically. Call
generateLayerDataTexture() after modifying tiles.
- WebGL renderer only (no Canvas fallback)
- Cannot use multiple tilesets on a single layer
gpuLayer.putTileAt(5, 10, 10);
gpuLayer.generateLayerDataTexture();
TilemapLayerBase
Both TilemapLayer and TilemapGPULayer extend TilemapLayerBase (src/tilemaps/TilemapLayerBase.js), which extends GameObject. The base class provides all tile query, manipulation, and collision methods. It includes these component mixins: Alpha, BlendMode, ComputedSize, Depth, ElapseTimer, Flip, GetBounds, Lighting, Mask, Origin, RenderNodes, Transform, Visible, ScrollFactor, and Arcade Physics Collision.
Common Patterns
Creating from 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);
}
The layerID passed to createLayer must match the layer name in Tiled exactly. Group layer children are flattened with a 'ParentGroup/Layer' naming convention.
Multiple Layers
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);
foreground.setDepth(10);
A layer can use multiple tilesets (CPU layer only):
const tiles1 = map.addTilesetImage('terrain', 'terrain-img');
const tiles2 = map.addTilesetImage('objects', 'objects-img');
const layer = map.createLayer('Ground', [tiles1, tiles2]);
Creating a Blank Layer
const map = this.add.tilemap('map');
const tileset = map.addTilesetImage('terrain', 'terrain-img');
const layer = map.createBlankLayer('dynamic', tileset, 0, 0, 50, 50, 32, 32);
layer.fill(1);
layer.putTileAt(5, 10, 10);
Collision Setup
There are several ways to enable tile collision for Arcade Physics:
layer.setCollision([1, 2, 3]);
layer.setCollisionBetween(1, 50);
layer.setCollisionByProperty({ collides: true });
layer.setCollisionByExclusion([-1, 0]);
layer.setCollisionFromCollisionGroup();
All collision methods on TilemapLayerBase mirror methods on Tilemap but don't require a layer parameter. On the Tilemap, you can pass a layer reference or use the "current layer":
map.setLayer('Ground');
map.setCollision([1, 2, 3]);
map.setCollision([1, 2, 3], true, true, 'Ground');
Physics Integration (Arcade)
this.physics.add.collider(player, groundLayer);
this.physics.add.collider(player, groundLayer, (sprite, tile) => {
if (tile.index === 5) {
}
});
this.physics.add.overlap(player, groundLayer, (sprite, tile) => {
});
The layer must have collision set on its tiles (via setCollision* methods) for physics to detect them. The layer itself has collisionCategory and collisionMask properties for collision filtering.
Tile Properties
Tiles can have custom properties set in Tiled's tileset editor:
const tile = layer.getTileAt(10, 5);
console.log(tile.properties.damage);
console.log(tile.properties.type);
layer.setCollisionByProperty({ collides: true });
layer.setCollisionByProperty({ type: ['wall', 'rock'] });
Tile Callbacks
map.setTileIndexCallback([5, 6, 7], (sprite, tile) => {
console.log('Hit tile', tile.index, 'at', tile.x, tile.y);
}, this);
map.setTileLocationCallback(10, 10, 5, 5, (sprite, tile) => {
}, this);
const tile = layer.getTileAt(10, 5);
tile.collisionCallback = (sprite, tile) => {
};
Tile callbacks require an active physics collider/overlap between the body and the layer.
Querying Tiles
const tile = layer.getTileAt(10, 5);
const tile = layer.getTileAt(10, 5, true);
const tile = layer.getTileAtWorldXY(worldX, worldY);
const exists = layer.hasTileAt(10, 5);
const tiles = layer.getTilesWithin(0, 0, 10, 10);
const tiles = layer.getTilesWithinWorldXY(x, y, w, h);
const tiles = layer.getTilesWithinShape(circle);
const water = layer.filterTiles(t => t.properties.type === 'water');
const spawn = layer.findTile(t => t.properties.isSpawn);
layer.forEachTile(t => { });
Modifying Tiles at Runtime
layer.putTileAt(5, 10, 10);
layer.putTileAtWorldXY(5, worldX, worldY);
layer.putTilesAt([[1, 2], [3, 4]], 10, 10);
layer.removeTileAt(10, 10);
layer.fill(1, 0, 0, 10, 10);
layer.replaceByIndex(5, 10);
layer.copy(0, 0, 5, 5, 20, 20);
layer.randomize(0, 0, 10, 10, [1, 2, 3, 4]);
layer.weightedRandomize([{ index: 1, weight: 4 }, { index: 2, weight: 1 }], 0, 0, 10, 10);
layer.shuffle(0, 0, 10, 10);
Coordinate Conversion
const tileXY = layer.worldToTileXY(worldX, worldY);
const worldXY = layer.tileToWorldXY(tileX, tileY);
const vec = new Phaser.Math.Vector2();
layer.worldToTileXY(worldX, worldY, true, vec);
Object Layers (Tiled)
Tiled object layers define points, rectangles, and sprite placement. Use createFromObjects on the Tilemap:
const enemies = map.createFromObjects('Enemies', {
gid: 26,
classType: Enemy
});
const coins = map.createFromObjects('Items', {
name: 'coin',
key: 'coin-texture',
frame: 0
});
const spawns = map.createFromObjects('Spawns', {
type: 'player-spawn'
});
const objectLayer = map.getObjectLayer('Enemies');
objectLayer.objects.forEach(obj => {
console.log(obj.name, obj.x, obj.y, obj.properties);
});
createFromObjects(layerName, config, useTileset) config options: id, gid, name, type, classType (default Sprite), scene, container, key, frame, ignoreTileset.
Animated Tiles
Tile animations are defined in Tiled's tileset editor and parsed automatically. Both TilemapLayer and TilemapGPULayer support animated tiles. The TilemapLayerBase uses ElapseTimer to track animation time via preUpdate.
Isometric, Hexagonal, and Staggered Maps
const map = this.add.tilemap('iso-map');
const tileset = map.addTilesetImage('iso-tiles', 'iso-img');
const layer = map.createLayer('Ground', tileset);
const tile = layer.getIsoTileAtWorldXY(worldX, worldY);
The map orientation property is set from Tiled data. Coordinate conversion functions are automatically selected based on orientation.
API Quick Reference
Tilemap (data container - not rendered)
| Method | Description |
|---|
addTilesetImage(name, key, tw, th, margin, spacing, gid, offset) | Link tileset name to texture |
createLayer(layerID, tileset, x, y, gpu) | Create layer (gpu=true for GPU layer) |
createBlankLayer(name, tileset, x, y, w, h, tw, th) | Create empty layer for procedural maps |
createFromObjects(layerName, config, useTileset) | Convert Tiled objects to Sprites |
getObjectLayer(name) | Get raw object layer data |
setLayer(layer) | Set current active layer for shorthand methods |
Most tile query/collision/manipulation methods exist on both Tilemap (with extra layer param) and TilemapLayerBase (without). Prefer calling on the layer directly.
TilemapLayerBase (rendered layer - CPU and GPU)
Collision:
setCollision(indexes), setCollisionBetween(start, stop), setCollisionByProperty(props), setCollisionByExclusion(indexes), setCollisionFromCollisionGroup(), setTileIndexCallback(indexes, cb, ctx), setTileLocationCallback(x, y, w, h, cb, ctx)
Tile queries:
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)
Tile manipulation:
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)
Coordinates:
worldToTileXY(wx, wy, snap, vec, cam), tileToWorldXY(tx, ty, vec, cam)
TilemapGPULayer (additional)
| Method | Description |
|---|
generateLayerDataTexture() | Regenerate GPU texture after tile edits |
Tile Properties
index (number, -1=empty), x/y (tile coords), pixelX/pixelY (pixel pos relative to layer), width/height, properties (object from Tiled), collideLeft/Right/Up/Down (boolean), collisionCallback (function), tint (number), rotation (number), alpha, flipX/flipY, physics (object for engine data)
Gotchas
-
Tileset name must match Tiled exactly. The first argument to addTilesetImage is the tileset name as defined in Tiled, not the Phaser texture key. If they don't match, you get null back and a console warning.
-
Layer name must match Tiled exactly. createLayer takes the layer name from Tiled (or layer index). Group layer children are prefixed with 'GroupName/LayerName'.
-
Each layer can only be created once. Calling createLayer with the same layer ID twice returns null with a warning. The layer data can only be associated with one layer Game Object.
-
setCollision must be called before physics colliders work. Without marking tiles as collidable, this.physics.add.collider() will pass through all tiles.
-
TilemapGPULayer is orthographic only. It does not support isometric, hexagonal, or staggered maps. It also only supports a single tileset per layer.
-
TilemapGPULayer requires manual texture regeneration. After calling putTileAt or other edit methods, call generateLayerDataTexture() or the changes won't appear.
-
"Collection of Images" tilesets are not supported. The Tiled parser requires all tiles in a tileset to be in a single image. Embedded tilesets in the exported JSON are required.
-
Tile index -1 means empty. Many methods return null for empty tiles by default. Pass nonNull: true to get a Tile object with index === -1 instead.
-
insertNull in tilemap factory. When creating a tilemap, insertNull: true stores null for empty tiles instead of Tile objects with index -1. Saves memory for large sparse maps but prevents dynamic tile placement in empty cells.
-
Tile callbacks only fire with active physics. setTileIndexCallback and setTileLocationCallback require a physics collider or overlap between the body and the layer to trigger.
-
Layer position and Tiled offset. If x and y are not specified in createLayer, they default to the layer offset defined in Tiled, not (0, 0).
Source File Map
| File | Purpose |
|---|
src/tilemaps/Tilemap.js | Main data container with all map-level methods |
src/tilemaps/TilemapFactory.js | Registers this.add.tilemap() on GameObjectFactory |
src/tilemaps/TilemapLayerBase.js | Shared base for CPU and GPU layers (extends GameObject) |
src/tilemaps/TilemapLayer.js | CPU-rendered layer (multi-tileset, all orientations) |
src/tilemaps/TilemapGPULayer.js | GPU-accelerated layer (v4, WebGL, orthographic, single tileset) |
src/tilemaps/Tile.js | Individual tile data (index, position, collision, properties) |
src/tilemaps/Tileset.js | Tileset data (name, firstgid, dimensions, image) |
src/tilemaps/components/ | Pure functions: SetCollision, GetTileAt, PutTileAt, SetTileIndexCallback, etc. |
src/tilemaps/parsers/tiled/ | Tiled JSON parsers: ParseJSONTiled, ParseTileLayers, ParseObjectLayers, ParseTilesets, BuildTilesetIndex |
src/tilemaps/ParseToTilemap.js | Orchestrates parsing and Tilemap creation |
src/tilemaps/mapdata/LayerData.js | Layer data structure |