You can click here to read part 1
You can click here to read part 3
Last time we worked on this tutorial we had the isometric world being created, the player being moved to the center of the map and collisions with the tilemap working properly. What we did not have working properly was sprite depth, resulting in the player always rendering above the walls. So in this final part of the tutorial we are going to be adding the finishing touches to this little project.
Before we get working on the sprite depth issue, I want to first address an issue that will help clean up the rooms that the algorithm generates. Currently the random walk can sometimes leave a single wall tile surrounded by floor tiles. You may have seen them yourself if you followed the last tutorial.
Fixing this little issue doesn’t take a whole lot of extra work. Inside of our _generate_map() function, right after the nested loop that is used to flag walls (right before we move the player at the end), we can make another nested for loop that will check for walls, and if the wall has a floor on all 4 sides, we just turn it into a floor.
for x in room_size.x:
for y in room_size.y:
if get_cell(x, y) == WALL:
if get_cell(x-1, y) == GROUND && get_cell(x+1, y) == GROUND && get_cell(x, y-1) == GROUND && get_cell(x, y+1) == GROUND:
set_cell(x, y, GROUND);
With that additional little bit of code, the maps that this algorithm generates will look a lot cleaner, and will not have random walls sticking out like a sore thumb in our rooms. Now we can focus on getting the player to render behind the walls.
Thankfully Godot 3.2.1 has some functionality built into the tilemap node that will help us out with this. Inside of the tilemap properties we can enable Y Sort, and change the Tile Origin from Top Left to Center.
This is only the first part of the solution. I am not sure if you ever tried to manually place tiles on the tilemap, but if you did you might have noticed something funny. Because my sprites are positioned at the bottom on the PNG file, the tiles are drawn below their coordinates on the grid. To fix this, we need to go inside of the tileset data and offset the texture and shape.
With my sprites I found that an offiset of -64 to the texture and collision shape puts it just about where it needs to be. If you are using your own sprites, your adjustments will probably be different than mine. You will need to apply the offset to the wall and floor tiles and even the void tile (because it has a collision shape). With all of this work complete, when we run our game we get something that looks like this.
The player is rendering behind the walls, just like we want, but he is also rendering behind the floor when he moves through it. The reason this is happening is because of the isometric perspective the player will register as above / below the floor at the half way point. This works perfectly for walls but not so great for the floor.
The easiest solution that was proposed to me was to split the floor and walls into separate tilemap nodes. You have the wall tilemap node as a child of the floor and only turn ysort on for the wall tilemap node. Any object that we want to render behind the walls (like the player or enemies) needs to be a child of the wall tilemap node.
This change will break the game if you try to run it, we need to modify our script so that the game will run. At the start of the script we can make an onready variable to reference the new tilemap and the player scene.
onready var Walls = $WallTiles;
onready var Player = $WallTiles/KinematicBody2D;
After these references have been made we go to where we were adding the walls and insert our new variable before the function calls (do the same thing when we move the player). To remove the single walls we now add a floor tile to the floor tilemap and set the wall tile to be the default value of -1. You can edit your code to look like mine.
#This is inside of our _generate_map() function
for x in room_size.x:
for y in room_size.y:
if get_cell(x, y) == GROUND:
if get_cell(x, y-1) == VOID:
Walls.set_cell(x, y-1, WALL);
if get_cell(x-1, y) == VOID:
Walls.set_cell(x-1, y, WALL);
for x in room_size.x:
for y in room_size.y:
if Walls.get_cell(x, y) == WALL:
if get_cell(x-1, y) == GROUND && get_cell(x+1, y) == GROUND && get_cell(x, y-1) == GROUND && get_cell(x, y+1) == GROUND:
Walls.set_cell(x, y, -1);
set_cell(x, y, GROUND);
Player.position = map_to_world(room_size / 2);
Now the player will always render above the floor, and will render behind the walls, like we want. There is one last thing that frustrates me with this solution. Currently the walls will always render in front of the floor when I had intended the floors to render in front of the walls. If you look carefully at the screenshot, you can see that the walls will cut off the floor, and I don’t really like the way that it looks.
The solution that I came up with is to lift up the walls so that the bottom of the wall lines up with the top of the floor. I adjusted the offsets to the walls from -64 to -69 and it seemed to work. But now that the walls have been lifted up, there will be a little gap under the walls that I don’t think looks very good. To fix that, I add another floor beneath every wall.
for x in room_size.x:
for y in room_size.y:
if get_cell(x, y) == GROUND:
if get_cell(x, y-1) == VOID:
Walls.set_cell(x, y-1, WALL);
set_cell(x, y-1, GROUND);
if get_cell(x-1, y) == VOID:
Walls.set_cell(x-1, y, WALL);
set_cell(x-1, y, GROUND);
With that, we should have the players rendering behind the walls when he is above them and in front of them when below. The walls should have a floor underneath them so that there isn’t a gap below the walls. If you notice that the player is still clipping behind the wall at the corners, you just need to make the hitbox for the wall or the player slightly bigger and it should fix the problem.
Until Next Time
– Steven