In this lesson, we’ll create doors that block the player’s progression. Each door can be opened temporarily by walking on a linked pressure plate.
We’ll also draw a dynamic line to show which plate activates which door.
In the scoreboard series, we used signals to communicate between nodes.
This section will demonstrate how to code mechanics like levers, switches, and other interactive objects that trigger something else.
Usually, we prefer keeping objects as independent as possible. A classical approach is to use node paths: providing the pressure plate with a path to the door node. We’d have to provide the nodes manually with the path of the other node they need to communicate with.
In our case, since the two nodes are supposed to work together exclusively, a simpler approach is possible.
We will set up each door as a child of a pressure plate node. It makes it easy to open the door from the pressure plate’s script and draw a line connecting them.
Note: The door being a child of the pressure plate in the node tree is unrelated to its location in the scene! We’re still free to move the door around and place it wherever we want. The scene structure is only to find each door easily from plates.
We prepared several scenes to save you repetitive steps.
In the ObstacleCourse_Part2/obstacles/doors/
directory,
you’ll find a horizontal door, a vertical door, and a pressure
plate.
The two doors use a
node as their root to block the player.On the other hand, the pressure plate uses an
node to receive a signal when the player walks over it.All scenes also come with two animations named “closed” and “open.” Feel free to play the animations in the editor to see how they look.
To implement this mechanic, we need to:
Let’s do this!
We don’t want the doors to stay open forever. After a few seconds, they should close again. But we want to change this duration for each door, depending on how far it is from its pressure plate.
To do that, we need a
. Also, we want a way to tweak the timer’s duration for each pressure plate instance. The problem is, when instancing a scene, we don’t have access to its children.How can we modify a scene’s child when the child isn’t accessible in the editor?
Let’s find out.
Open the PressurePlate.tscn
scene, and add a node.
Then, attach a script to the root node.
Our first task is to make it easy to adjust the
Timer.wait_time
property from the Inspector when
adding PressurePlate instances to the obstacle course.
The timer is hidden in the PressurePlate scene, but we want to adjust its duration from the outside.
We will use a proxy property called open_time
that we
can directly modify in the Inspector. To do that, we use the
export
keyword when defining a variable in our script.
# The duration the linked door stays open in seconds.
export(float, 0.0, 100.0) var open_time := 1.0
We give open_time
a default value of 1.0
seconds.
The values in parentheses after the export
keyword are
optional. They tell Godot that our variable is a float
and
that we want a lower limit of 0.0
and an upper limit of
100.0
seconds in the Inspector.
After saving the script, you should see the Open Time property appear in the Inspector whenever you select the PressurePlate root node.
If the Open Time property does not appear, go to the Scene menu and select Reload Current Scene.
We now use the value of open_time
to set the
Timer.wait_time
property.
onready var timer := $Timer
func _ready() -> void:
= open_time timer.wait_time
We called the open_time
variable a “proxy” because we
don’t use it directly in our code, except to set another property on a
child node.
In Godot, we often create proxy properties to tweak children’s values.
We will now prepare the line that links a pressure plate to its door. This helps the player to understand the obstacle course and decide where to go.
Add a
node to the PressurePlate scene and move it up the tree, so it draws behind the plate.The
node is perfect for drawing lines and curves in the game. It lets you control the line’s width, color, width profile, etc.By default, it has a blue color. We can change it to a red tone to
match the pressure plate’s beam. The red we’re using is
ff1852
.
In the Inspector, change the line color to the palette’s red tone.
It uses an array of
points and connects them visually.In our case, there will be 3
points:
0
: aligned with the scene’s origin, at
(0,0)
.1
: aligned on the X-axis with the door, on the
Y-axis with the plate.2
: aligned with the door (wherever it is).This setup will always work and give us a nice 90
-degree
angle.
We need to calculate the
points at runtime to draw a correct connection between the pressure plate and the door.Back in the PressurePlate’s script, we first get the reference to the Door and
nodes.onready var line := $Line2D
# We'll add a door as a child of each PressurePlate instance in the obstacle
# course.
onready var door := $Door
We don’t have a Door node in the PressurePlate scene, but we can still declare variables that point to a Door node. We need to make sure to have door children for each instance in the obstacle course.
To draw the line, we update its points
array.
func _ready() -> void:
# ...
# We update the line's points to draw an L-shaped line between the pressure
# plate and the door.
= [
line.points # The first point is centered on the pressure plate.
Vector2.ZERO,# The second point is to the left or right of the pressure plate,
# vertically aligned with the door.
Vector2(door.position.x, 0.0),
# The last point is centered on the door.
door.position ]
If you create a scene with a PressurePlate instance and a DoorHorizontal instance as a child of it, you will see a line connecting the two.
When running the scene above, we get the following result.
Lastly, we need to code the interaction between the player and the plate.
When the character’s
enters the plate, we want to:When the timer runs out, we want to:
When the player enters the plate, we need to trigger the
nodes for both the door and the plate.We start by getting both of those in the PressurePlate script.
onready var animation_player := $AnimationPlayer
onready var door_animation_player := $Door/AnimationPlayer
We then connect the PressurePlate’s
body_entered
and the ’s timeout
signals:
func _ready() -> void:
# ...
connect("body_entered", self, "_on_Area2D_body_entered")
connect("timeout", self, "_on_Timer_timeout") timer.
We can move on to implementing those two callback functions. Let’s
start with _on_Area2D_body_entered()
.
The only node capable of interacting with the pressure plate is the player, so we don’t need extra checks to ensure that the detected body is correct.
When the body_entered
signal emits, we know that the
player touched the plate. We play the open animations and start the
timer to close the door when it runs out.
func _on_Area2D_body_entered(godot: Node) -> void:
play("open")
animation_player.play("open")
door_animation_player.start() timer.
The _on_Timer_timout()
function gets called when the
timer times out. There, we play the closed animations on the door and
the pressure plate.
func _on_Timer_timeout() -> void:
play("closed")
animation_player.play("closed") door_animation_player.
The animations enable and disable the door’s collision shape, allowing the player to pass only when the door is open.
With this, the mechanic is complete and we’re ready to test the pressure plate. Create a scene with an instance of the Godot character, a pressure plate, and a door as a child of the pressure plate.
When you walk on the plate, the door should open. It should then close after a few moments.
Currently, when the door gets triggered, the line stays red. We will add a keyframe to change the color in each animation.
Open the PressurePlate scene, and click on the
. Select the “closed” animation.Then, select the
node. Notice how keys appear next to properties in the inspector. Click on the key next to the color:Godot will ask you if you want to create a “RESET” track. Leave the defaults, and press “Create”:
This creates a key in the “closed” animation.
Now select the “open” animation, and with the 3dff6e
.
Click the key icon again to insert a new track and key with the green color. This change will make the line turn green when opening the door.
If you feel fancy, you could animate the color from red to green instead of switching it immediately, but for our needs, even an abrupt change looks good enough.
Games are complex beasts, so we often forget the requirements for our code to work. Even in small projects, it’s easy to make mistakes.
Godot has a built-in warning mechanism to warn us when a node isn’t set up correctly.
You already saw that with the
node. When creating an , it initially displays a warning sign.We can hover over the icon to get a help message or click to show it in a popup.
We can use the same warning system in our code!
To do so, we need two things:
tool
keyword._get_configuration_warning()
function.Any script with the tool
keyword on a code line runs
in the Godot editor.
This keyword allows us to create editor tools and plugins. For example, we use it in this course for the Practices dock.
For now, we’ll just use it for our node warning.
Add the following code to complete the PressurePlate script.
# By convention, we place the tool keyword at the top of the script, before the
# extends line.
tool
extends Area2D
# ...
# This function tells Godot to display a warning sign next to a node when it
# returns a message. If it returns an empty string, however, nothing happens.
func _get_configuration_warning() -> String:
# We ensure that the pressure plate has a child node named "Door" and that
# it is a StaticBody2D.
var door_node := get_node_or_null("Door") as StaticBody2D
if not door_node:
return "This node needs a child node named Door, either an instance of DoorHorizontal or DoorVertical."
return ""
In the function, we check if PressurePlate has a child named
Door using the get_node_or_null()
function. This
function returns null
if the node doesn’t exist.
The as
keyword allows us only to keep the node if it is
of a specific type. In this case, . Otherwise, the node gets replaced with
null
.
Note: We don’t use the existing door
variable directly because it updates once when we open the scene in the
editor. But the _get_configuration_warning()
will be called
multiple times by Godot while we work on the scene, and we want to get
the correct warning when someone adds or removes a Door
node.
If we now use the PressurePlate scene without a Door child node, we’ll get the warning symbol and message in the popup.
Add a DoorHorizontal or DoorVertical instance as a child, and the warning disappears.
Time to practice!
Open the practice Turning on the lights.
We have a tiny level with a pressure plate.
Your task is to code the interaction between Godot and the Switch to turn on a light.
Here is the complete code for PressurePlate.gd
source
file.
# By convention, we place the tool keyword at the top of the script, before the
# extends line.
tool
extends Area2D
# The duration the linked door stays open in seconds.
export(float, 0.0, 100.0) var open_time := 1.0
onready var timer := $Timer
onready var line := $Line2D
# We'll add a door as a child of each PressurePlate instance in the obstacle
# course.
onready var door := $Door
onready var animation_player := $AnimationPlayer
onready var door_animation_player := $Door/AnimationPlayer
func _ready() -> void:
= open_time
timer.wait_time # We update the line's points to draw an L-shaped line between the pressure
# plate and the door.
= [
line.points # The first point is centered on the pressure plate.
Vector2.ZERO,# The second point is to the left or right of the pressure plate,
# vertically aligned with the door.
Vector2(door.position.x, 0.0),
# The last point is centered on the door.
door.position
]connect("body_entered", self, "_on_Area2D_body_entered")
connect("timeout", self, "_on_Timer_timeout")
timer.
func _on_Area2D_body_entered(godot: Node) -> void:
play("open")
animation_player.play("open")
door_animation_player.start()
timer.
func _on_Timer_timeout() -> void:
play("closed")
animation_player.play("closed")
door_animation_player.
# This function tells Godot to display a warning sign next to a node when it
# returns a message. If it returns an empty string, however, nothing happens.
func _get_configuration_warning() -> String:
# We ensure that the pressure plate has a child node named "Door" and that
# it is a StaticBody2D.
var door_node := get_node_or_null("Door") as StaticBody2D
if not door_node:
return "This node needs a child node named Door, either an instance of DoorHorizontal or DoorVertical."
return ""
In the next lesson, we’ll redesign the obstacle course level using the new mechanics we worked on: pickups and pressure doors.