In this lesson, we’ll add code to play the push_down
animation when the player enters the wall’s , and place our new obstacle in a game
level.
Attach a new script to the PushWall node.
We start by getting our
and nodes.onready var area := $Area2D
onready var animation_player := $AnimationPlayer
Next, we connect the body_entered
signal and
play the push_down
animation when the signal emits.
func _ready() -> void:
# This signal emits when a physics body, like the player, enters the area.
connect("body_entered", self, "_on_Area2D_body_entered")
area.
# The Area2D.body_entered signal always emits along with an argument, the
# physics body that entered the area. That's why we must include an argument in
# this callback function.
func _on_Area2D_body_entered(body: Node) -> void:
play("push_down") animation_player.
The body_entered
signal emits when a physics
body enters the node’s collision shape.
There are three types of physics bodies areas can detect:
, , and , which we use for our player.We can now add the PushWall to our ObstacleCourse scene and test it.
Open the ObstacleCourse.tscn
scene we created several
lessons ago.
You should see two bridges with walls and a gap. We left them to add PushWall instances.
Add a new
node as a child of the Course node and rename it to Obstacles. We’ll add our obstacles as a child of it.We use a node to group our obstacles in the scene tree, and it needs to be a
node to change the draw order of the character, the course, and the obstacles. When you nest nodes, they all work together to manage the draw order of their children for you.We can now place the PushWall.tscn
scene in the spot
over the first bridge with snapping.
Select the Obstacles node and press
Ctrl+Shift+A to instantiate the
PushWall.tscn
as a child.
We should use grid snapping to align the PushWall instances precisely with the level.
To turn on grid snapping, press Shift+G or click the grid icon with a magnet in the toolbar.
Then, click the three vertical dots next to the grid snapping icon and click Configure Snap…
Set the Grid Step to Vector2(64, 64)
. This
corresponds to half the level tilemap’s cell size and allows us to place
the PushWall in the middle of a cell.
Click and drag your PushWall instance to move it over the first bridge, where we left an empty spot between the walls.
Then, press Ctrl+D to duplicate the PushWall and drag the new copy over the second bridge.
Your game level should now look like this.
And your scene tree should have the following nodes.
If you run the scene, you’ll see the PushWall has an issue: it plays the push animation at the start of the game, even though we haven’t walked over the area yet.
It also happens if you open the PushWall.tscn
file and
run the wall in isolation.
Looking back at the PushWall’s code, there’s only one way to trigger the animation: if the area detects a physics body entering it.
func _ready() -> void:
# This signal emits when a physics body, like the player, enters the area.
connect("body_entered", self, "_on_Area2D_body_entered")
area.
# The Area2D.body_entered signal always emits along with an argument, the
# physics body that entered the area. That's why we must include an argument in
# this callback function.
func _on_Area2D_body_entered(body: Node) -> void:
play("push_down") animation_player.
The PushWall scene has a physics body that overlaps with the area: the
node.The area detects its sibling, the static body, and emits the
body_entered
signal.
It’s a common issue when coding physics interactions.
Note how, to figure it out, we opened the scene and tried to reproduce the problem in a simpler context than the level. That’s how you figure out the cause of bugs and solve them.
To solve our bug, we need the area to ignore the static body and to only detect the player’s character.
To do so, we’ll use collision layers and masks. Let’s first set them up, and we’ll then see how they work.
In the PushWall scene, select the
node and expand the Collision category in the Inspector. You’ll see two lists of buttons: Layer and Mask.Click the two lit buttons with a 1
to turn off both
layer 1
and mask 1
, then click button
2
below the Mask label.
This means the area will only detect physics bodies and areas
assigned to Collision -> Layer number 2
.
Then, open the Godot.tscn
scene in the
ObstacleCourse/
directory and select the Godot node.
Set its Collision -> Layer to 2
only and
leave its Collision -> Mask at 1
only.
With that, the character’s physics body will be on physics layer
2
, allowing the PushWall’s area to detect it, and
the player will collide with everything on physics layer
1
.
We can now trigger the PushWall animation as the player enters its
node.In Godot, physics bodies and areas have collision layers and masks.
These layers and masks filter which nodes interact and can significantly increase your game’s performance.
You can think of collision layers as tags assigned to physics nodes. A given node can have multiple collision layers.
Collision masks tell the node which tags it can look at.
Take two physics nodes, A and B. Node B will detect node A if node A has a tag (collision layer) that node B looks at (collision mask).
That’s the only way physics interactions will work in Godot 4.0.
In Godot 3.0, node A will also detect node B in the case above. But it’s not intuitive, so the developers decided to simplify the system for Godot 4.0.
In the next lesson, we’ll add invisible walls to the obstacle course so that the player can’t cut corners by walking over the empty space.
Open the practice Picking up the gems.
We fleshed out the “maze” scene from the previous practice with gems, but the player cannot pick them up!
Your task is to change the gems’ collision mask and add the missing code so that the player can pick up the gems.
To complete this practice, you’ll need to open the
Gem.tscn
scene and update the script and the Gem
node’s properties. The Gem node is an .
So far, to connect an area’s signal, we needed first to get the node
and then call its connect()
function, like so.
onready var area := $Area2D
func _ready() -> void:
connect("body_entered", self, "_on_Area2D_body_entered") area.
In this practice, because the Gem itself is an
node, we don’t need to get the node.You can directly call the connect()
function without
getting an extra node.
func _ready() -> void:
# Call the connect() function on the node to which you attached this script.
connect(...)
In GDScript, when you call functions directly in a script, it is implied that you call functions on the node to which you attached the script.
Some signals always come with arguments, like the area’s
body_entered
and area_entered
signals. When a
physics body enters an area, you generally want to know which body that
was.
That’s why this signal, when it emits, also emits the physics body entering the area as an argument that gets passed to the callback function.
This is why the callback function must have a parameter: to give you access to the physics body.
At the start of the lesson, we connected the wall’s area to a
callback function, _on_Area2D_body_entered()
.
We mentioned that the Area2D.body_entered
signal always
emits along with an argument, the physics body that just entered the
area.
How do we know that?
There are two places you can check to know if a signal emits an argument or not:
First, there’s the Node dock, next to the Inspector. It lists the arguments emitted with the signal and their type. But it’s not always easy to read.
The built-in code reference is the most comprehensive place to find information about signals. It’s a technical manual that describes how each function, variable, etc. works.
We’ll explore it in greater detail a bit later in the course.
You can press F1 to open the reference search window.
In this popup window, you can search for any node type, function name, signal name, etc.
If you search and open the body_entered
signal, you’ll
get more information. There, you can see the signal’s description, its
arguments, and the arguments’ description.
Here is the complete code for the PushWall.gd
source
file.
extends Node2D
onready var area := $Area2D
onready var animation_player := $AnimationPlayer
func _ready() -> void:
# This signal emits when a physics body, like the player, enters the area.
connect("body_entered", self, "_on_Area2D_body_entered")
area.
# The Area2D.body_entered signal always emits along with an argument, the
# physics body that entered the area. That's why we must include an argument in
# this callback function.
func _on_Area2D_body_entered(body: Node) -> void:
play("push_down")
animation_player.