doc 1 of 3

Event Bus — the pattern to use last, not first

A coin is collected. The HUD counter should increase, an achievement should tick, a sound should play, and the tutorial popup should dismiss itself. The coin is fifteen nodes away from every one of them.

The tempting fix is a global object every script can reach — Events.coin_collected.emit() — and everything that cares subscribes. It works immediately, it feels clean, and it is the single easiest way to turn a comprehensible project into one where nothing can be traced.

This shelf teaches the pattern and the discipline that keeps it from eating your codebase, because those are genuinely the same lesson.

An honest disclaimer, up front#

Godot's documentation does not describe an event bus. It doesn't endorse one, doesn't provide one, and doesn't mention the idea anywhere. What it does have is a page called Autoloads versus regular nodes that lists three problems with exactly this kind of global object:

  • Global state — one object holds data everything depends on, so a failure spreads everywhere.
  • Global access — anything can call it from anywhere, so when something goes wrong the list of suspects is "the entire project".
  • Global resource allocation — it's hard to judge how much the global thing should own.

Those are real, and this shelf agrees with them. The event bus is a community pattern that solves a real problem the engine leaves to you, and it is worth using narrowly. Anyone who presents it as best practice is telling you about a convention, not about Godot.

Try these four things first#

By the time a bus is the right answer, you should have rejected all of these for a stated reason:

Reach forWhen
@onready var x := %ChildTalking to your own children. Direct, typed, obvious
@export var target: Node2DTalking to something outside your scene — assign it in the Inspector
A plain signal upwardReporting to whoever owns you. This is the default
GroupsBroadcasting to many things of a kind: get_tree().call_group("enemies", "alert")

Call down, signal up. A parent knows its children concretely and calls their methods; a child never calls upward — it emits a past-tense signal (died, coin_collected) and stays ignorant of who listens. Most communication that feels like it needs a bus is actually one missing signal on an intermediate node.

The bus earns its place only when the emitter and the listener are in genuinely different branches of the tree, with no sensible shared ancestor to mediate — and when wiring a reference would mean threading it through five layers that don't care about it.

What this shelf teaches#

PartYou buildThe idea
01An Events autoload that declares only signals, plus connection hygieneThe bus itself is twenty lines; the rules are the work
02Naming conventions, a debug tracer, and the review test for bus abuseHow to still understand this in six months

The one-sentence rule#

A bus carries announcements, never commands.

Events.player_died.emit() is an announcement — a fact about the world that already happened. Whoever cares reacts. Events.show_game_over_screen.emit() is a command dressed as a signal: the emitter has decided what should happen and is using the bus as a remote control. The second one is how buses turn into invisible spaghetti, and part 02 shows how to spot it in your own code.

Past tense in the name is not a style preference. It's the test.