3. Signals: one press, one method

2 min

See a signal travel from a node to the method it reaches.

Running scene HUD.tscn
Button
Label Presses: 0
  1. Button emits pressedThe Button node fires its built-in pressed signal. It does not know who is listening.
  2. The connection routes itIn _ready the HUD connected pressed to its own method, so the engine calls that method now.
  3. The handler runs_on_button_pressed increments the counter and writes the Label's text. The Label changes; the Button never touched it.
hud.gd press the button
1extends CanvasLayer
2
3var presses := 0
4
5func _ready() -> void:
6 $Button.pressed.connect(_on_button_pressed)
7
8func _on_button_pressed() -> void:
9 presses += 1
10 $Label.text = "Presses: %d" % presses

A signal is how one node tells others that something happened without knowing who is listening. Press the button below and watch the three things that occur: the button emits pressed, the connection routes it, and the handler runs and changes the label.

Coming from This replaces direct references, UnityEvents, C# events and most Blueprint event wiring. The emitter never holds a reference to the listener.

To finish: Press the button and watch the trace complete.

Notes

The written version of this lesson, for after you have done it.

A signal is a named event a node can emit. Built-in nodes come with theirs: Button has pressed, Area2D has body_entered, Timer has timeout. Your scripts declare more with the signal keyword and fire them with emit().

The emitter never holds a reference to the listener. The Button in this lesson does not know a Label exists; the HUD script connected pressed to its own method, and the engine calls that method when the signal fires. Delete the Label and the Button still works. That decoupling is the whole point, and it is what replaces GetComponent chains, UnityEvents, and most Blueprint event wiring.

Connect in code when the connection depends on runtime state, and in the editor's Node dock when it is fixed for the scene. Both produce the same call. Name handlers _on_<node>_<signal> so the connection reads as a sentence: _on_button_pressed, _on_coin_body_entered.

Two more forms you will meet in the first week. A connection can be made one-shot with CONNECT_ONE_SHOT, so it disconnects itself after firing once, which is what a tutorial popup or a first-contact trigger wants. And a function can wait for a signal with await: `await $Timer.timeout` pauses that function until the timer fires and then continues on the next line, which is Godot's replacement for coroutines.

Signals carry arguments. Declare them typed, `signal health_changed(current: int, max_health: int)`, and every listener receives those values in that order, which is how a health bar updates without ever asking the player for its numbers. When a listener needs something the emitter does not know, bind it at connection time: `$Timer.timeout.connect(spawn.bind("goblin"))` calls spawn("goblin") on every timeout.

Try it in the editor five minutes, in a real project

  1. Create a scene with a CanvasLayer root and add a Button and a Label as its children. Attach a script to the root.
  2. Select the Button, open the Node dock beside the Inspector, double-click the pressed signal and connect it to the root. Godot writes _on_button_pressed for you.
  3. Inside that method set $Label.text to a counter you increment. Run the scene and press the button.
  4. Delete the Label and run again: the Button still works and only the handler errors, because the emitter never knew the Label existed.
  1. Button emits pressed. The Button node fires its built-in pressed signal. It does not know who is listening.
  2. The connection routes it. In _ready the HUD connected pressed to its own method, so the engine calls that method now.
  3. The handler runs. _on_button_pressed increments the counter and writes the Label's text. The Label changes; the Button never touched it.