-
Notifications
You must be signed in to change notification settings - Fork 4
Tilemaps
There is a good tutorial series, Modular Game Worlds in Phaser 3:
- Static Maps — tilesets, map from array, map from CSV, map from Tiled JSON, collisions with Arcade Physics
- Dynamic Platformer — painting tiles, converting tiles to sprites, Arcade Physics hitboxes
- Procedural Dungeon — blank layers, fill tiles, put tiles at
- Meet Matter.js — Matter Physics, convert tilemap layer, collision shapes
The basic procedure is:
- Load tileset images
- Load a tilemap file, if using one
- Create a tilemap object
- Assign tileset images to the tilemap
- Create tilemap layers, specifying one or more tilesets for each
- Mark colliding tiles in a layer, if you want collisions
- Add a collider for a tile layer, if you want collisions
- Map size must be fixed.
- Use only a Tileset Image, not an Image Collection.
- If you don't embed the tileset here, you'll need to do it when exporting.
- Embed tilesets
- Detach templates
- Resolve object types and properties
I find Tiled pretty painful to use.
Every tilemap layer needs at least one tileset image to render. You assign this to the map with addTilesetImage().
load.image() is fine for this, but you can also use load.spritesheet() (with the appropriate frame dimensions) if you want to use the tileset image as a multiframe texture for other game objects like sprites.
If you get any errors loading images, fix those first before creating the tilemap.
- Tiled JSON map example
- Tiled multiple tilesets example
- Static Maps: Building a Map in Tiled etc.
Create the tilemap from the loaded map data:
const tilemap = this.add.tilemap('map');
If you've forgotten the tile layer or tileset names, you can find them there:
console.log('tile layers', tilemap.layers);
console.log('tilesets', tilemap.tilesets);
For each tileset, add an image:
const tileset = tilemap.addTilesetImage('name', 'textureKey');
For each layer, create a layer with at least one tileset:
const tileLayer = tilemap.createLayer('tileLayerName', tileset);
The lazy way to create tilemap layers:
for (const { name } of tilemap.layers) {
tilemap.createLayer(name, tilemap.tilesets);
}
collide()
and add.collider()
test only the tile IDs or locations set by setCollision()
etc., setTileIndexCallback()
, and setTileLocationCallback()
. For the last two, your callback can return false
to cancel separation with the tile.
overlap()
and add.overlap()
test all intersecting tiles, including empty tiles. You can filter these out pretty easily if you need to.
this.physics.add.overlap(
sprite,
tilemapLayer,
function onOverlap(sprite, tile) {
console.log("overlap", tile.x, tile.y);
},
function process(sprite, tile) {
// Tile not empty
return tile.index > -1;
}
);
const map = this.add.tilemap(/*…*/);
const [firstObjectLayer] = map.objects;
console.info('Object properties in %s:', firstObjectLayer.name);
console.table(firstObjectLayer.objects);
console.info('Object custom properties in %s:', firstObjectLayer.name);
console.table(firstObjectLayer.objects.map(o => o.properties));
"Ghost collisions" can happen in physics engines when two colliding bodies are next to one another, e.g. a player trying to walk over two neighboring ground tiles. The order in which the collisions are resolved by the engine can cause "unrealistic" effects, e.g. the player being stopping dead in their tracks on flat ground. See http://www.iforce2d.net/b2dtut/ghost-vertices for more info.
When working with tilemaps and Matter, there are a couple ways to mitigate this issue:
- Add chamfer to bodies, i.e. round the edges, or use circular bodies to reduce the impact of the ghost collisions.
- Map out your level's hitboxes as as a few convex hulls instead of giving each tile a separate body. You can still use Tiled for this. > Create an object layer, and fill it with shapes, convert those shapes to Matter bodies in Phaser (see below).
- Use a library like hull.js to automatically figure out convex hulls from your tiles.
— tilemap/collision/matter ghost collisions
- Reduce the number of visible layers
- Reduce the number of visible tiles in the viewport (e.g., don't zoom out)
- Reduce the number of cameras (e.g., no minimaps)
For small static maps, you can draw tilemap layers onto a Render Texture (maybe 4096 × 4096 at largest) and then hide or destroy the originals.