15.autoloads

User Interface

Open the res://interface/OnScreenUI.tscn scene.

It contains two nodes.

At the moment, this user interface appears in the game but does nothing.

Autoloads

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

Event bus

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 Sprite child to rotate it or on an AnimationPlayer so it starts moving.

A calls a function on C

We also know how to communicate from a child to a parent: we emit a signal. For example, an AnimationPlayer might emit animation_finished, or an Area2D might emit body_entered.

B emits a signal that A is connected to

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 Area2D might emit body_entered, which the mob’s root node listens to and sets an AnimationPlayer to play a specific animation.

B emits a signal that A is connected to; then A calls a function on C

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.

D tells the Autoload to emit a signal C is connected to

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.

Building the autoload

Create a new script at the root, and call it Events.gd.

We need two signals:

  1. When a mob dies, we want it to dispatch a signal that contains the points the mob is worth. Let’s call this signal mob_died.
  2. When the player gets hit, we want to dispatch a signal containing the remaining health. Let’s call this signal 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:

  1. The robot to dispatch player_health_changed when hit.
  2. The mob to dispatch mob_died when it dies.
  3. The UI to listen to both and update accordingly.

Robot

In res://robot/Robot.gd, locate the set_health function. Add the line:

Events.emit_signal("player_health_changed", health)

At the bottom of it. Every time the health is changed, the Events autoload will emit this signal.

Mob

In res://mobs/Mob.gd, locate the _die() function. Add the line:

Events.emit_signal("mob_died", points)

At the bottom of it. Every time a mob dies, the Events autoload will emit this signal.

UI

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
    Events.connect("mob_died", self, "_on_Events_mob_died")
    Events.connect("player_health_changed", self, "_on_player_health_changed")

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!

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!

What’s next?

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.