Tilemaps
Loading Tiled JSON maps, rendering layers, building collision, and mutating tiles at runtime.
Phaser 4’s tilemap system reads Tiled JSON exports. You author the map externally and let Phaser handle rendering, collision, and runtime mutation.
Loading a Tiled map
Two files are involved: the map JSON and the tileset image referenced by it.
preload() {
this.load.tilemapTiledJSON('level-1', 'assets/maps/level-1.json');
this.load.image('tiles', 'assets/tilesets/world.png');
}
Then assemble the map and its layers:
create() {
const map = this.make.tilemap({ key: 'level-1' });
// The first argument is the tileset name *as defined in Tiled*; the second
// is the cache key of the loaded image. They are often the same.
const tileset = map.addTilesetImage('world', 'tiles');
const ground = map.createLayer('Ground', tileset, 0, 0);
const walls = map.createLayer('Walls', tileset, 0, 0);
const decor = map.createLayer('Decor', tileset, 0, 0);
}
The third and fourth args are world coordinates of the layer’s top-left corner — usually 0, 0.
Collision
Three idiomatic ways to mark which tiles collide:
// 1. By tile index — exact tile IDs from the tileset.
walls.setCollision([12, 13, 25, 26]);
// 2. By range.
walls.setCollisionBetween(1, 99);
// 3. By a Tiled custom property — best if you control the source.
walls.setCollisionByProperty({ collides: true });
Then add a collider:
this.physics.add.collider(player, walls);
For one-way platforms, slopes, and other angle-aware collision, see the Tilemaps API reference — TilemapLayer.setCollision* methods and the Arcade-specific TileCollisionGroup cover most cases.
Object layers
Tiled object layers are the canonical way to place gameplay markers — player spawn, enemy positions, doors. They come through as a list of plain objects:
const spawns = map.getObjectLayer('Spawns');
spawns?.objects.forEach((obj) => {
switch (obj.name) {
case 'player': this.player = this.physics.add.sprite(obj.x!, obj.y!, 'player'); break;
case 'enemy': this.enemies.create(obj.x!, obj.y!, 'enemy'); break;
}
});
Each object’s properties carries the Tiled custom properties as {name, type, value} entries — flatten them with a small helper if you find yourself doing it a lot.
Camera bounds
Almost always pair the map with the camera:
this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
this.physics.world.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
this.cameras.main.startFollow(this.player);
Without this, the camera will happily scroll past the edges of the map and into the void.
Runtime mutation
Layers expose direct tile access:
const tile = walls.getTileAtWorldXY(player.x, player.y - 32);
if (tile?.properties.breakable) {
walls.removeTileAt(tile.x, tile.y);
}
// Swap every instance of one tile for another.
ground.swapByIndex(45, 67);
// Carve a line of tiles.
for (let x = 0; x < 8; x++) walls.putTileAt(12, startX + x, startY);
Note that tile coordinates here are tile-space (0, 0 is the top-left tile), not pixels. Use the WorldXY variants when you have pixel coordinates and the indexed variants when you have tile coordinates.
Performance
- Static layers are cheap. Phaser batches a layer into one or a few draw calls. A 200×200 map renders for nearly free.
- Per-tile properties cost lookup time. If you find yourself iterating every tile per frame, cache the result.
- Culling is on by default. Off-camera tiles are skipped. Don’t disable culling without measuring first.
Related
- Arcade Physics — how tile collisions resolve.
- Cameras — set bounds to match the map.
- Loader & Assets —
tilemapTiledJSONand image loaders.