As we saw in the previous post, it is remarkably easy to create "one-way" platforms using sprites. However, what about using tilemaps? That is what this post is about. (I have not actually created "pass-through" platforms using tiles in my example code but have commented on how I might implement it).
"One-way" platforms using Tilemaps
We can create platforms that you can "jump through" using arcade physics, by selectively setting the checkCollision property of the arcade physics body to true or false. For example, for platforms that you can walk past, jump from the bottom but land on top, you set body.checkCollision.up = true but down, left and right to false.
...but how does collision detection between tiles and sprites work
In the case of sprite vs sprite collision detection using arcade physics, sprites are first given physics bodies. Then a collider is set. Then the arcade physics engine takes care of the rest.
With tilemaps too, collision detection and separation against sprites with physics bodies can indeeed by carried out by the arcade physics engine. However, tiles are not given a physics body as such but must first be "marked" as collidable so that arcade physics engine knows which tiles to carry out collision detection against.
Specifically, tiles are marked for collision detection by tilemap layer, and there are a variety of methods provided by Phaser to do so.
setCollision(indexes [, collides] [, recalculateFaces] [, layer] [, updateLayer])
setCollisionBetween(start, stop [, collides] [, recalculateFaces] [, layer])
setCollisionByExclusion(indexes [, collides] [, recalculateFaces] [, layer])
setCollisionByProperty(properties [, collides] [, recalculateFaces] [, layer])
setCollisionFromCollisionGroup( [collides] [, recalculateFaces] [, layer])
Once specific tiles are marked as "collidable", then you simply set up arcade physics collider, in the same way as sprite vs sprite collision, except in the case of tilesmaps, it is sprite vs tile layer.
So, suppose we create a simple world like the one at the top of this post, using the following tileset, and we want the "floating" platforms to be normal solid platforms that you cannot jump through.
I created the world using TILED, and exported the map as JSON file.
Then, you would load the data from preload...
this.load.tilemapTiledJSON('map', 'PhaserWorld.json');
this.load.image('tileset', 'platformer_tiles.png');
and then create your map as per usual, from the create method.
this.map = this.make.tilemap({ key: 'map', tileWidth: 16, tileHeight: 16 });
const tileset = this.map.addTilesetImage('platformer_tiles','tileset');
this.platforms = this.map.createLayer('Tile Layer 1', tileset, 0, 0);
You could then find out the indices of the 4 tiles that make up the floating platform and make them collidable using setCollision or setCollisionBetween.
However, when you have a large number of tiles in your tileset, keeping track of tile indices can be tiresome. It is easier to set a "collides" property of the relevant tiles to true, as follows.
Then there is one extra step to make the arcade physics engine recognise these tiles as collidable in the relevant layer.
this.platforms.setCollisionByProperty({collides: true});
and then you set up a collider as usual.
this.physics.add.collider(this.player, this.platforms);
However, the above creates "solid" platforms that you cannot pass through.
Suppose we wanted the "ground" tiles forming the land to the right side of the game world to be "passable" so that Mr Dude can walk past and jump vertically onto the next "ledge" above - how to make arcade physics engine recognise collisions only in certain directions?
What is the tilemap equivalent of checkCollision.top?
Looking at the Phaser documentation for tile object, the following members/properties seem to be the equivalent of arcade physics body checkCollision member.
and indeed there is a method to set these properties.
But the above method appears to be for setting these properties for an individual tile in a particular layer. How can we set the property of all the tile of the same index of a particular layer to the desired set of properties (ie setCollision(false,false,true,false)? Is there a way of doing so with the setCollisionByProperty method?
setCollisionByProperty == setCollision(true, true, true, true)
If you look at the underlying code of the setCollisionByProperty method, you will find that it loops through all the tiles within the relevant layer looking for the tiles with the relevant property, and calls another method called setTileCollision, which in turn is defined as follow. It is actually calling the setCollision method above, with all the directions set to true!
var SetTileCollision = function (tile, collides)
{
if (collides)
{
tile.setCollision(true, true, true, true, false);
}
else
{
tile.resetCollision(false);
}
Having understood the mechanism, it is easy to implement a "one-way" version of the above. We can set a custom property so that we can identify which tiles should have their colliding faces set as oneway, say "OneWay" = true, like so:
Read the tilemap data like any other JSON file
The tilemap and tiles data exported from TILED is..obviously, a JSON file. Phaser provides methods to read the JSON map file without you having to figure out how the various bits of data are stored. However, you can manipulate the file just like any other JSON file. The custom properties is a simple way of attributing particular properties to a particular tile, which can be read in Javascript.
Hence we can cycle through all the tiles of a given layer, setting the collision flags appropriately, like so:
this.platforms.forEachTile(tile => {
if (tile.properties["OneWay"]) {
tile.setCollision(false, false, true, false);
}
});
"Fall Through" Platforms
Now that we have figured out how to set collider properties for each of the faces of a tile, we can implement fall through platforms in the same way as in the sprite example.
The crucial (and I have not been able to figure out a way around it, using tiles) is that the collider properties are set not at individual tile level, but for a particular tile within a tileset. Hence if the collideUp property for say, tile #1 is set to false, all the tile #1 on the particular layer will have the property set to false. This might present a problem if you have multiple tile #1 "on top" of each other. The way to get around it is..simply design your game so you don't have such situations.
The Code
Key references
Comments