Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Build event-driven, decoupled Godot 4.x gameplay with signals and node groups: declare and emit custom signals, connect with Callables (incl. bind/one-shot), and broadcast to many nodes via groups and call_group. Use when wiring node communication in a Godot project, replacing tight references with signals, emitting/connecting events, or porting 3.x connect("sig", self, "method") code.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 12% | 0% |
| case-11 | ✓→✓ | = Same ✓ | 50% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 152% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 125% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 37% | 0% |
Decouple nodes with the observer pattern (signals) and act on many nodes at once (groups), instead of hard-coding references between scenes. Targets Godot 4.3+.
up, wave cleared) without holding direct references to them.
"save every checkpoint").
When not to use: raw signal syntax basics → godot-gdscript; scene structure and instancing → godot-nodes-scenes. For cross-scene global events, emit from an autoload (see godot-nodes-scenes).
connects to it. This keeps the child reusable and ignorant of who listens.
emit() them when the event occurs.sig.connect(_on_sig)), optionally in the editor's Nodedock. Use CONNECT_ONE_SHOT for fire-once, bind() to pass extra context.
get_tree().get_nodes_in_group(...) or call_group(...).
is_connected() to avoid duplicate connections.
gdscript# coin.gd (reusable pickup — knows nothing about the player or HUD) extends Area2D signal collected(value: int) func _on_body_entered(body: Node) -> void: if body.is_in_group("player"): collected.emit(10) queue_free()
gdscript# level.gd (the parent wires the coin to game state) func _ready() -> void: for coin in get_tree().get_nodes_in_group("coins"): coin.collected.connect(_on_coin_collected) func _on_coin_collected(value: int) -> void: GameState.add_score(value)
gdscriptfunc _ready() -> void: # Fire exactly once, then auto-disconnect. $Door.opened.connect(_on_door_opened, CONNECT_ONE_SHOT) # bind() appends arguments supplied at connect time (after the signal's own args). $RedButton.pressed.connect(_on_button.bind("red")) func _on_button(color: String) -> void: print("Pressed the %s button" % color)
gdscriptfunc pause_all_enemies() -> void: # Call a method on every node in the "enemies" group (no-op if missing). get_tree().call_group("enemies", "set_paused", true) func count_enemies() -> int: return get_tree().get_nodes_in_group("enemies").size()
Add a node to a group from code or via the editor's Node > Groups tab:
gdscriptfunc _ready() -> void: add_to_group("enemies") # remove_from_group("enemies") to leave
gdscriptfunc open_chest() -> void: $AnimationPlayer.play("open") await $AnimationPlayer.animation_finished # pause until it emits spawn_loot()
connect("died", self, "_on_died") →died.connect(_on_died). The target is implied by the Callable. The legacy Object.connect("died", Callable(self, "_on_died")) works but the method-name string form does not.
_ready()after re-adding a node stacks callbacks. Guard with if not sig.is_connected(cb): sig.connect(cb).
Godot auto-disconnecting when the connected object is freed (it does for nodes).
name share membership. Namespace group names if that matters.
call_group silently ignores nodes lacking the method. Typos in the method namefail quietly — prefer a typed signal when the contract matters.
declare typed params and emit exactly those.
timeouts, and signals vs. direct calls trade-offs, read references/signal-patterns.md.
godot-gdscript — signal/await syntax fundamentals.godot-nodes-scenes — autoloads for global event buses.game-ai — state machines that often drive and consume these events.Other measured skills in the registry, with their headline benchmark lift.