This is Part 4 of the Godot series. Read Previous Parts here.
All the code is here, under Part 4
The problem in our previous code was if we fell over the edge, we kept falling.
We need to detect and correct this. There are multiple ways to do it:
- The easiest way is to have the level closed off, so the player can never fall. This is a design fix
- You can do a simple check, so if the player goes below a certain y axis level, it would kill the player. This is simple but very inelegant.
- A better way is to have a "fall detect" zone, so if the player enters it (by falling, for example), we can take action, like respawning the player.
The 3rd way is the most elegant and teaches us useful skills that can be used in other situations.
The way to create this fall zone is: On Level1, add a new node Area2d, and a child node CollisionShape2d:
Rename Area2d to fallzone, and for CollisionShape2d, choose rectangle
We now want to move fallzone. To make our job easy, with fallzone selected, click this:
It will ensure when we move fallzone, the children also move. Now we can move fallzone to below the level:
Now, resize the fallzone. Select CollisionShape2d, and make sure it covers all teh empty area below the level:
Im not bothering to have the fallzone to the left of the screen, as the high wall on the left means I cannot fall off that way (though if this changes, we'll haveto update the fallzone).
So we now have this fallzone below our level:
How does it help us? Well, we can now write some code to detect when our player enters this fall zone, and what to do when it does.
Code to detect when the player enters fallzone
Click on fall zone, and on the right, next to Inspector, you will see a Node menu. In this menu is a body_entered function.
This function will trigger a signal when any body (player or otherwise) enters it, and which will then allow us to call some code.
Double click it:
We now need to connect it to a script. Choose Player and click connect:
In the player.gd script, you should see a new function:
func _on_fallzone_body_entered(body): pass # Replace with function body.
We need to update the code here. We can do a lot of fancy stuff, but for now, we will just restart the level. Update the code:
func _on_fallzone_body_entered(body): get_tree().change_scene("res://level1.tscn")
get_tree() gets all the root level functions, and on that, we change the scene to our level1.tscn.
So if you now fall off the edge, the level will restart. Try it out.
And our level just restarts.
Let's add more hazards now.
Adding hazards (spikes) to our level
From now on, I will move faster, not describing every step, as you should know how to add scenes etc now. If you get stuck, look at previous examples.
Let's add spikes to our level now. The player will "die" if they touch them, same as falling off a cliff, and respawn.
Create a new scene, with root as Area2d. Have 2 children– Sprite and CollisionShape2d. Rename the top node to spikes.
Save it as spikes.tscn
For the Sprite, drag spikes_top.png to Texture in the Inspector, and set the shape of CollisionShape2d as a rectangle.
Draw a rectangle over the spikes to make sure they cover them:
To make it easy, make sure you have clicked the box you can't select the children of the node.
On Level1 screen, add a new Node2d object (a child of Level1) and name it spikes. Why spikes? Because we will be adding multiple spikes to it.
With spikes selected, make sure you click on "instance to a child scene":
and select spikes.tscn you created.
As with the fallzone, we want to detect when a player enters the spike zone. We will use the body_entered() function again and create another script in player.gd.
Use the same code as before:
func _on_spikes_body_entered(body): get_tree().change_scene("res://level1.tscn")
Move the spike to somewhere useful so we can test:
and run the code. You should see the level restart:
Good. Now right click on our spike and duplicate it a few times:
and place the spikes in different places.
Make sure you do a quick playthru to ensure the level isnt too hard.
Follow the exact same process as for spikes and add a few coins. The only difference is, rather than kill the player, it will give us points.
this is what is should look like:
Connect the body_entered function to the player script.
In the player.gd file, add this to the top:
var coins_found = 0
And update the function you got:
func _on_coin_body_entered(body): coins_found += 1 print("coins found =", coins_found)
Check it works by running the game.
Removing the coin
We do see the coin print incrementing, but the coin stays on the screen. The next step is to remove the coin.
Add a script to the coin.tscn file, and add another _on_coin_body_entered function, but this time in coin.gd
func _on_coin_body_entered(_body): queue_free()
The queue_free() function deletes the coin.
Test your code to check the coin vanishes;
then copy the current coin a few times to create multiple coins. Check it works for all. Im going to add 3 coins for now.
We will fix the print later, so rather than just print the number of coins, it displays it on the screen. But for now, let's add one more condition: If the player collects all the coins, s/he wins.
For this, we need a new variable TOTAL_COINS.
We could just set it to 3, as we know we have 3 coins, but the problem is, if we later add more coins, the code will fail. we need to dynamically count how many coins we have. Luckily, thats easy in Godot.
We update the ready function, as it's called everytime the scene is ready:
func _ready(): coins_found = 0 TOTAL_COINS=get_parent().get_node("coins").get_child_count()
The first line, coins_found=0 – Im fixing a bug, as if you died, it remembered how many coins you had. This way, everytime the scene reloads, coins are set to zero.
In the 2nd line, we get the parent node, and then the child node coins. Visually:
We go up to Level 1, then get child node coins, and count the number of children in, which will give us the number of coins.
The advantage now is, if we add more coins, the code will keep working.
Test it out, and confirm you win when you get all 3 coins.
Later on, we'll add a proper win screen. For now, let's add some enemies.
Note: I found a critical bug that froze my code– it was because I wasn't using layers properly. In a quick post, I will explain how layer masks work in Godot.