--- name: event-bus description: Use when implementing decoupled communication between nodes — global EventBus autoload with typed signals --- # Event Bus in Godot 4.3+ A global signal hub that lets unrelated nodes communicate without holding references to each other. All examples target Godot 4.3+ with no deprecated APIs. > **Related skills:** **component-system** for direct signal communication between components, **csharp-signals** for C#-specific signal patterns, **dependency-injection** for alternative decoupling approaches, **ability-system** for an EventBus usage example with ability events. --- ## 1. What is an Event Bus An EventBus is a singleton autoload that acts as a central registry for signals. Instead of nodes connecting directly to each other, every node connects to (or emits on) the shared EventBus. This removes the need for one node to hold a reference to another. ``` Without EventBus With EventBus ────────────── ────────────────────────── NodeA ──signal──► NodeB NodeA ──emit──► EventBus ──signal──► NodeB ──signal──► NodeC ──signal──► NodeD ``` **Flow diagram** ``` ┌─────────┐ emit(player_died) ┌───────────┐ player_died ┌──────────┐ │ NodeA │ ────────────────────► │ EventBus │ ───────────────► │ NodeB │ │(Player) │ │(Autoload) │ │ (UI) │ └─────────┘ └───────────┘ ───────────────► └──────────┘ player_died ┌──────────┐ │ NodeC │ │(AudioMgr)│ └──────────┘ ``` NodeA emits the signal. NodeB and NodeC each connected to EventBus independently. Neither knows the other exists. --- ## 2. When to Use vs Direct Signals | Scenario | Recommended approach | |--------------------------------------------|-------------------------------| | Parent notifying its own child | Direct signal or method call | | Child notifying its parent | Direct signal (bubble up) | | Two nodes with the same parent | Direct signal via parent | | Completely unrelated nodes in the tree | Event bus | | UI reacting to gameplay state changes | Event bus | | Audio manager reacting to game events | Event bus | | Data manager / save system reacting | Event bus | | Tight, performance-sensitive inner loop | Direct method call | **Rule of thumb:** if you would otherwise need `get_node("../../SomeDistantNode")` or a hard-coded NodePath, the event bus is a better fit. --- ## 3. Basic EventBus Create `res://autoloads/event_bus.gd` (or `EventBus.cs`), then register it in **Project → Project Settings → Globals → Autoload** with the name `EventBus`. ### GDScript (`autoloads/event_bus.gd`) ```gdscript extends Node ## Emitted when the player character has died. signal player_died ## Emitted whenever the score changes. signal score_changed(new_score: int) ## Emitted when a level finishes successfully. signal level_completed(level_id: int) ## Emitted when the player picks up a collectible. signal item_collected(item_name: String) ## Emitted when the player's health changes. signal health_changed(current: int, maximum: int) ``` ### C# (`Autoloads/EventBus.cs`) ```csharp using Godot; /// /// Global signal hub. Register as an autoload named "EventBus". /// public partial class EventBus : Node { /// Emitted when the player character has died. [Signal] public delegate void PlayerDiedEventHandler(); /// Emitted whenever the score changes. [Signal] public delegate void ScoreChangedEventHandler(int newScore); /// Emitted when a level finishes successfully. [Signal] public delegate void LevelCompletedEventHandler(int levelId); /// Emitted when the player picks up a collectible. [Signal] public delegate void ItemCollectedEventHandler(string itemName); /// Emitted when the player's health changes. [Signal] public delegate void HealthChangedEventHandler(int current, int maximum); } ``` --- ## 4. Connecting to Events Consumers connect in `_ready()`. In C#, always disconnect in `_ExitTree()` to avoid dangling delegates and memory leaks. ### GDScript ```gdscript extends CanvasLayer # GDScript connections are reference-counted and cleaned up automatically # when the node is freed, but explicit disconnection is still good practice # for long-lived nodes that reconnect frequently. func _ready() -> void: EventBus.player_died.connect(_on_player_died) EventBus.score_changed.connect(_on_score_changed) EventBus.health_changed.connect(_on_health_changed) func _exit_tree() -> void: EventBus.player_died.disconnect(_on_player_died) EventBus.score_changed.disconnect(_on_score_changed) EventBus.health_changed.disconnect(_on_health_changed) func _on_player_died() -> void: $DeathScreen.show() func _on_score_changed(new_score: int) -> void: $ScoreLabel.text = "Score: %d" % new_score func _on_health_changed(current: int, maximum: int) -> void: $HealthBar.value = float(current) / float(maximum) * 100.0 ``` ### C# ```csharp using Godot; public partial class HudLayer : CanvasLayer { private EventBus _eventBus; public override void _Ready() { _eventBus = GetNode("/root/EventBus"); // Connect using strongly-typed delegate handlers _eventBus.PlayerDied += OnPlayerDied; _eventBus.ScoreChanged += OnScoreChanged; _eventBus.HealthChanged += OnHealthChanged; } // IMPORTANT: Always disconnect in _ExitTree() in C#. // C# delegates are not automatically cleaned up when a node is freed. // Failing to disconnect causes the EventBus to hold a reference to the // freed node, leading to memory leaks and InvalidOperationExceptions. public override void _ExitTree() { _eventBus.PlayerDied -= OnPlayerDied; _eventBus.ScoreChanged -= OnScoreChanged; _eventBus.HealthChanged -= OnHealthChanged; } private void OnPlayerDied() { GetNode("DeathScreen").Show(); } private void OnScoreChanged(int newScore) { GetNode