06.coding-the-push-mechanic

Coding and using the push wall

In this lesson, we’ll add code to play the push_down animation when the player enters the wall’s Area2D, and place our new obstacle in a game level.

Attach a new script to the PushWall node.

We start by getting our Area2D and AnimationPlayer nodes.

onready var area := $Area2D
onready var animation_player := $AnimationPlayer

Next, we connect the Area2D node’s 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.
    area.connect("body_entered", self, "_on_Area2D_body_entered")


# 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:
    animation_player.play("push_down")

The body_entered signal emits when a physics body enters the Area2D node’s collision shape.

There are three types of physics bodies areas can detect: StaticBody2D, RigidBody2D, and KinematicBody2D, which we use for our player.

Using the push wall obstacle

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.

Adding a node to group obstacles

Add a new YSort 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 YSort node to change the draw order of the character, the course, and the obstacles. When you nest YSort 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.

Setting up grid snapping

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.

Testing and fixing the wall push

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.
    area.connect("body_entered", self, "_on_Area2D_body_entered")


# 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:
    animation_player.play("push_down")

The PushWall scene has a physics body that overlaps with the area: the StaticBody2D 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.

Solving the animation bug

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 Area2D 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 Area2D node.

How collision layers and masks work

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.

Practice: picking up the gems

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 Area2D.

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:
    area.connect("body_entered", self, "_on_Area2D_body_entered")

In this practice, because the Gem itself is an Area2D 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.

Your questions

Why did we need a body argument in _on_body_entered()?

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.

How do I know when a signal callback needs a parameter?

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:

  1. The Node dock.
  2. The built-in code reference.

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.

The code

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.
    area.connect("body_entered", self, "_on_Area2D_body_entered")


# 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:
    animation_player.play("push_down")