Open the res://interface/OnScreenUI.tscn
scene.
It contains two nodes.
At the moment, this user interface appears in the game but does nothing.
As you’ve seen, we often access data through node paths in Godot.
But in most games, there are nodes you will want to access from anywhere. A few examples:
You might use a shared Resource
for some of these. But
if you need to access nodes, then an autoload is more
appropriate.
For those nodes that should be accessible from anywhere, Godot introduces the concept of Autoloads.
Note: They’re also called “singletons” in the documentation, but we prefer not using that name because programmers generally mean something else when they say “singleton.”
We know how to communicate from a parent to a child. We call a function on the child. For example, a player character might call a function on a
child to rotate it or on an so it starts moving.We also know how to communicate from a child to a parent: we emit
a signal. For example, an animation_finished
, or an might emit body_entered
.
It’s more tricky, but we also know how to communicate from child to
child. We emit a signal from the first child to the parent.
Then the parent calls a function on the other child. For
example, a mob’s body_entered
, which
the mob’s root node listens to and sets an to play a specific animation.
Now, the tricky one:
How do you communicate between arbitrary nodes?
For example, how does a killed mob send loot/score to the player? The mob doesn’t know where in the tree the player is. The player doesn’t know where in the tree the mob is.
Sometimes, you can connect both through an
_on_Area2D_body_entered
or such signal. But it’s not always
the case. What if a mob is killed at a distance by a bullet? How would
the player character and the mob communicate then?
The answer is an autoload that relays signals.
We call this method an event bus, because it’s a bus that transports events.
An event bus is a node with predefined events that we can call and listen to from anywhere. Think of it as a broadcasting radio station that anyone can call.
Because it’s an autoload, we know it will always be there and available.
Create a new script at the root, and call it
Events.gd
.
We need two signals:
mob_died
.player_health_changed
.Here’s the entire script for the event bus:
extends Node
signal mob_died(score_value)
signal player_health_changed(new_health)
We need to tell Godot to autoload this node.
Open Project Settings…
Click the AutoLoad tab, and then click the browse icon.
Select the file you just created, and press Add.
That’s it. You have an autoload now. Anywhere in the whole project,
if you use the name Events
, you will be referring to this
node.
What’s left to do is to use it!
We want:
player_health_changed
when
hit.mob_died
when it dies.In res://robot/Robot.gd
, locate the
set_health
function. Add the line:
emit_signal("player_health_changed", health) Events.
At the bottom of it. Every time the health is changed, the
Events
autoload will emit this signal.
In res://mobs/Mob.gd
, locate the _die()
function. Add the line:
emit_signal("mob_died", points) Events.
At the bottom of it. Every time a mob dies, the Events
autoload will emit this signal.
Finally, let’s listen to those signals in the UI.
Open res://interface/OnScreenUI.gd
. You will find an
empty scene.
Try to reflect on how you’d connect the event bus and what should happen.
Here’s the code for getting the two nodes and connecting to events to get you started.
extends Control
var _score := 0
onready var _health_bar := $HealthBar
onready var _score_label := $ScoreLabel
func _ready() -> void:
# set the score to 0 at the beginning
_set_score(0)
# Connect to the global event bus
connect("mob_died", self, "_on_Events_mob_died")
Events.connect("player_health_changed", self, "_on_player_health_changed") Events.
We have a _score
variable to count the points.
We also connect to two functions that don’t exist. It’s up to you to implement them!
_on_Events_mob_died
receives an int
, which
is some points to add to _score
. When that happens, we want
to update the score (make sure to convert the int
to a
with the str()
function!)._on_player_health_changed
receives a health value and
should change the _health_bar
’s health
property.Below, we’ll give you the code, so stop here if you prefer trying by yourself.
Here’s a function that updates the label:
# Run to update the score and the label
func _set_score(new_score: int) -> void:
_score = new_score
_score_label.text = str(_score).pad_zeros(5)
And finally, here are the two connected functions:
# This function runs when the global event bus emits a "mob_died" signal
func _on_Events_mob_died(mob_score_value: int) -> void:
_set_score(_score + mob_score_value)
# This function runs when the global event bus emits a "player_health_changed" signal
func _on_player_health_changed(health: int) -> void:
_health_bar.health = health
If you play the game, you’ll see life and score updates when the robot gets hit, or a mob dies.
Congrats!
You now have a playable game with infinite potential for variations in your hands. Once you’re satisfied with it, all that’s left is to export it so your friends can play it.