Event Bus Tutorial


In Godot, when you want objects to interact (say reacting to a button press), the standard way is with signals. This form of message passing helps reduce coupling, which is a wonderful thing. In my opinion, there is still too much coupling, but it's easy to fix.

Let's say you have a Button with a "change_colour" signal attached to a ColorRect. When the Button is pressed, it sets the ColorRect to a specific colour. If we drew that connection, it might look like this:

One Connection

If we added 2 more Buttons to control the colour of the ColorRect, we get this:

3 Connections

3 Connections still isn't too bad but it's common for multiple objects to receive a given signal. Multiple objects might need to know if the player dies, for example. If we add some more ColorRects, we end up with this:

9 Connections

3 Buttons sending a signal to 3 ColorRects gives 9 connections. Setting that up with the editor's GUI would be very tedious and if the receivers of the signal are created dynamically, this isn't possible. A common solution to this is to add all receivers to a Group. Then in the Button scripts you can loop through the Group and connect them. That code would look something like this:

func _ready() -> void:
    for receiver in get_tree().get_nodes_in_group("colour_change_receivers"):
        self.connect("change_colour", receiver, "on_change_colour")

This makes it much more manageable than manually connecting things via the GUI and it reduces coupling; while the Buttons are directly acting on each receiver, they don't know anything about each receiver other than that they are a member of the "colour_change_receivers" Group.

The Group approach can work well as long as nothing in your heirarchy is dynamic. In a highly dynamic scene, with a lot of senders and receivers, this is a mess. The solution that I prefer is an Event Bus.

With an Event Bus, anything sending a signal, sends it to the Event Bus and only the Event Bus. Similarly, receivers subscribe only to the Event Bus. This can greatly reduce the number of connections. Coupling is reduced because the senders and receivers know absolutely nothing about each other. In a highly dynamic scene, each receiver connects to the Event Bus as needed.

Event Bus

The code to do this is very simple and leverages all of the existing Signal framework. Simply create an Autoload object called EventBus that extends Node and has a list of your signals. Adding "#warning-ignore:unused_signal" before each signal will stop Godot from complaining that it's unused.

EventBus.gd:

extends Node

#warning-ignore:unused_signal
signal change_colour

In your code that sends a signal, you simply use this one line:

    EventBus.emit_signal("change_colour", color_value)

And finally, in your scripts where you want to receive a signal:

    EventBus.connect("change_colour", self, "on_change_colour")