GDSense how-to guide

Godot Signals in GDScript: Connect, Emit, and Debug

Connect a typed health signal to a HUD, handle the initial state, and trace missing or duplicate callbacks with a practical Godot example.

Published Updated

All documentation guides

The short answer

Declare a signal on the object that owns an event, connect it to a receiver once, and emit it when that event happens. In Godot 4 GDScript, use signal_name.connect(callback) and signal_name.emit(arguments). Keep the emitter independent of the HUD or other listener, initialize the listener explicitly, and verify the payload and connection lifecycle.

When should I use a signal instead of a direct method call?

Use a signal to announce something that happened when the sender should not need to know who responds. Health can announce a new value while a HUD, sound controller, and achievement system react independently. Use a direct call when one object explicitly asks another to perform an operation and owns that relationship.

This example uses a Node root with two children: Health (Node) and HealthLabel (Label). The Health script owns the health value; the root connects it to the Label. Create these exact node names before attaching the scripts below. The example intentionally has no Autoload, player movement, or combat system.

Godot introduction to signals

How do I declare and emit a typed health signal?

Declare the payload alongside the signal so the receiving code has a clear contract. Here damage is non-negative, health cannot drop below zero, and unchanged health produces no event. Those are deliberate game rules for this example; choose different rules explicitly if your game needs them.

res://scripts/health_component.gd — attach to Health
class_name HealthComponent
extends Node

signal health_changed(current: int)

var health: int = 100

func take_damage(amount: int) -> void:
    var next_health: int = maxi(health - maxi(amount, 0), 0)
    if next_health == health:
        return
    health = next_health
    health_changed.emit(health)

Keep in mind: Emitting an event does not store it for listeners that connect later. The current health value remains the source of truth for initial rendering.

How do I connect the HUD and show its initial value?

Connect from the scene root after its children are ready, then render the current value once. Passing the callback without parentheses connects the function itself; adding parentheses would call it immediately. This example wires the event in code, so do not also add a second connection in the editor.

To exercise the example, temporarily call health_component.take_damage(25) after the initial render in _ready(). The label should change from 100 to 75. For interactive testing, connect a test Button to a root method that calls the same damage function, then remove the test control when integrating the component.

res://scripts/health_demo.gd — attach to the root
extends Node

@onready var health_component: HealthComponent = $Health
@onready var health_label: Label = $HealthLabel

func _ready() -> void:
    health_component.health_changed.connect(_on_health_changed)
    _on_health_changed(health_component.health)

func _on_health_changed(current: int) -> void:
    health_label.text = "Health: %d" % current
Godot Signal.connect and connection rules

Why is a signal missing or firing more than expected?

Trace the event through three checkpoints: the state change, the emission, and the callback. A missing callback can mean the listener connected too late or to another instance. Extra callbacks can come from repeated emissions or multiple receivers—not only a connection problem.

Signal symptoms and the next fact to inspect
SymptomCheckUseful correction
HUD stays blank until damageIs there an explicit initial render?Read current health after connecting.
Callback never runsSame runtime instance? Connected before emission?Inspect the Remote scene tree and log at emit and callback.
Already-connected errorIs setup running twice or also wired in the editor?Use one connection owner; inspect is_connected before repeatable setup.
One action causes several updatesHow often is damage called? How many HUD instances exist?Count emissions and callbacks separately.
Reopened UI shows old dataDoes the new receiver bind to the current emitter?Rebind deliberately and initialize from current state.

Keep in mind: For a persistent emitter and a temporarily hidden receiver, decide whether hiding should keep the subscription. Hiding a node is not the same as freeing it; disconnect deliberately when that is the intended lifecycle.

How can GDSense help review signal wiring?

Provide both ends of the connection and the saved scene text that creates them. @scene --scripts supplies hierarchy and associated scripts, but it does not include serialized signal connections created in the editor. Use @file for the relevant .tscn file, or paste its connection entries, so those connections are visible. Review the text for secrets before sharing.

Ask for an event trace before changing code. A single emitter file cannot reveal a duplicate scene instance or an editor-created connection elsewhere in the scene. Saved scene text also cannot prove the current runtime instance count; include that observation from Godot when it matters.

Signal debugging request
@file res://scripts/health_component.gd
@file res://scripts/health_demo.gd
@file res://scenes/health_demo.tscn
The HUD updates twice for one hit after reopening the scene.
Trace damage calls, emissions, connections, and receiver instances separately.
Keep the health_changed(current: int) contract. Propose a minimal fix and a repeated-open test.
  • Initial display is 100 without needing an event.
  • Damage of 25 changes health to 75 and emits once.
  • Zero or negative damage does not change health or emit.
  • Damage larger than remaining health clamps to zero; further damage emits nothing.
  • Scene restart and repeated UI opening do not multiply callbacks.

Try this workflow inside Godot

Use GDSense to ask questions, attach the context you choose, and review proposed changes without leaving the editor.