For many games it is important to support several layers that characters can be placed upon. Scenarios could be flying characters or higher layers like bridges.
Therefore the concept of character layers was introduced to GridEngine. Character layers have two impacts on GridEngine:
Each character layer is mapped to a layer in the tilemap. The z-index (called depth in Phaser) of the character layer is determined by the z-index of the corresponding tilemap layer. Simply put, collisions only take part within the same character layer. We will get into more details later.
Let's first look at character layers using two examples.
One common use case for character layers is flying characters. Say you have a map with humans and flying creatures. You may want the flying creatures to move freely without colliding with items or characters on the ground. However, you want them to collide with other flying characters.
In order to achieve this, we create two character layers: ground and sky. In our code we can simply add the layers to the character configurations:
Now the monster birds will not collide with the player but with each other. However, you will notice that the player does not collide with the tilemap anymore. Furthermore the player will be rendered on top of a bird in some cases:
This is because we need to tell GridEngine which layer on the tilemap our character layers ground and sky correspond to. Then GridEngine knows which tilemap layers to take into account for tilemap collisions and further it can derive the rendering order of the char layers. To achieve that we will create a new tilemap layer for the sky:
It is important that the sky layer is above the ground layer. Otherwise the birds would be rendered below the ground layer.
Next we need to fill the whole sky layer with invisible non-colliding tiles. By that I mean tiles that do not contain any pixels and are non-colliding. Why did I do that and not just leave those blank? The reason for this is the collision. If there is no tile at all on any collision-relevant tile layer for the current character layer, then GridEngine will handle the position as colliding. So no tile is handled like a colliding tile.
We need to give the tilemap layer the property ge_charLayer with the value sky:
Now we add the layer property ge_charLayer with the value ground to the ground layer.
Now our two character layers behave as we intended. You can see the live example here.
Another common use case for several character layers are structures like bridges.
As you can see in the image we want characters to be below the bridge as well as on top of it.
Let's start by building a tilemap that we can use to support this feature.
We will create a tilemap with 3 layers: ground, buildings and bridge. ground will have the floor tiles. buildings will have tiles from the buildings that should show up on top of the ground tiles. The uppermost layer bridge will contain the tiles of the bridge that will be on top of the characters below the bridge.
Here is how the ground layer looks like:
And here is how the buildings layer looks like:
There are two questions that may arise:
What the heck are those jugs for?
Why is the bridge incomplete?
The jugs are only representing colliding tiles. They will be invisible later because the bridge layer will hide those. However, I used them to create a collision barrier on the ground level. This does not allow the character to walk around freely "below" the building but only in the corridor.
The bridge is incomplete because we want the player to be able to walk below the upper part of the building later. This can be best shown with a picture:
Now let's finally take a look at the bridge layer:
This layer is supposed to show up on top of all characters on the ground level. It will also be important for the collisions of all characters on bridge level.
Take care of tiles marked with the red "!" on the screenshot. These are invisible tiles. I placed non-colliding tiles there that don't have any pixels. Why did I do that and not just leave those blank? The reason for this is the collision. If there is no tile at all on any collision-relevant tile layer for the current character layer, then GridEngine will handle the position as colliding. So no tile is handled like a colliding tile.
But why aren't we taking a non-colliding non-invisible tile like the stairs tile to make it less confusing?
Well, the reason is that when leaving the bridge, this tile will overlap the upper part of our character, because she will already be on the ground char layer, which is below the bridge char layer. You can see the result here:
So now we need to make sure that characters on ground level do not collide with the bridge layer (and it's characters) and vice versa.
Therefore we set the ge_charLayer property of the buildings layer to ground and that of the bridge layer to bridge as shown in the last example.
Why are we giving the ge_charLayer property to the buildings layer and not the ground layer? Well, that is because we want the two layers buldings and ground to belong to the char layer ground. Or expressed differently: we want the chars of char layer ground to be rendered above both layers ground and buildings. By assigning ge_charLayer to a tilemap layer we define inbetween which tilemap layers our chars will be rendered. But which tilemap layers will be relevant for collisions of a certain char layer? For every tilemap layer l with the layer property ge_charLayer, GridEngine will take all consecutive tilemap layers below l that do not have the ge_charLayer property and put them into the same collision group:
In our example the collision group ground will contain both tilemap layers ground and buildings because buildings has the ge_charLayer property and ground is below buildings and does not have the ge_charLayer property.
If we start our game now, we will see that you can walk below the bridge as intended. But if you try to walk up the bridge you will see your character disappear below the stairs...
We are missing one thing so far: layer transitions.
In order to achieve this transition we simply use the setTransition() method of GridEngine. Let's write some code that will make sure that a character that enters the stairs will switch the layers correctly:
The following image shows the corresponding tiles that hold the transitions:
The upper tile will make any character switch from ground layer to bridge layer on entering and the lower tile will make the character switch from bridge to ground again.
Why did we not pick the same tile for both transitions? We could do that but it will allow the character to switch layers in an unintended way: The layer transition happens on entering the tile. So if the player walks on that tile from the ground layer she will be transferred to the bridge layer. If the character then immediately walks down again she will still be on the bridge level but not on the bridge. This is not what we want. By having both transitions on adjacent tiles we ensure that this can't happen.
The ge_alwaysTop layer property is not needed anymore with character layers. In order to place layers on top of a character we simply need to make sure that the layers in the tilemap are on top of the tilemap layer with the corresponding char layer:
If they include a character shadow that is part of the character sprite and are rendered on the sky character layer like in example 1, we can observe that this shadow is rendered on top of other characters. One solution for this is shown in this example.
There are unsolved issues when using character sprites that stretch over the size of multiple tiles. These issues impact the display depth (z-index) of character sprites. There can be ugly overlapping issues. The reason for this is that the whole sprite is rendered at the depth of the character layer. But some scenarios demand parts of the sprites to be rendered at different depths.
One example is our bridge example. We create a second character and let it follow our player. Then as soon as the player entered the bridge and gets transferred to the bridge character layer we stop. The following character will be one tile behind us but his head is rendered below our character. This is to be expected, because both characters are on different char layers. However, it is not the intended behaviour.