Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. Cheat Sheets
  3. GDScript
  4. GDScript Signals Cheat Sheet
GDScriptCheat Sheet

GDScript Signals Cheat Sheet

Quick-reference for Godot 4 signal patterns. Each section includes copy-ready snippets with inline output comments using real game scenarios.

On this page
  1. 1Declaring Custom Signals
  2. 2Connecting Signals
  3. 3Emitting Signals
  4. 4Built-in Signals
  5. 5Passing Extra Data with bind()
  6. 6One-shot and Deferred Connections
  7. 7Await Signals
  8. 8Disconnecting Signals
  9. 9Signal Bus Pattern (Autoload)
  10. 10Signals with Groups
  11. 11Common Signal Pitfalls
Declaring Custom SignalsConnecting SignalsEmitting SignalsBuilt-in SignalsPassing Extra Data with bind()One-shot and Deferred ConnectionsAwait SignalsDisconnecting SignalsSignal Bus Pattern (Autoload)Signals with GroupsCommon Signal Pitfalls

Declaring Custom Signals

Signals are declared at the top of a script. Godot 4 uses typed parameters for clarity.

Signal with no arguments
signal died

func take_lethal_hit() -> void:
    died.emit()
Signal with typed arguments
signal health_changed(new_hp: int, max_hp: int)
signal item_collected(item_name: String, value: int)

func take_damage(amount: int) -> void:
    hp -= amount
    health_changed.emit(hp, max_hp)
Signal declaration best practice
# Declare signals at the top of the script, before vars
signal enemy_died(enemy: Node2D, xp_reward: int)
signal wave_complete(wave_number: int)

var hp: int = 100

Signals are always declared before variables and functions by GDScript convention.

Connecting Signals

Use .connect() with a Callable reference. Godot 4 replaced the old string-based connect() from Godot 3.

Connect to a method
func _ready() -> void:
    $Enemy.died.connect(_on_enemy_died)

func _on_enemy_died() -> void:
    score += 100
Connect with an inline lambda
func _ready() -> void:
    $StartButton.pressed.connect(func():
        start_game()
    )
Connect via the $ shorthand
@onready var player: CharacterBody2D = $Player

func _ready() -> void:
    player.health_changed.connect(_update_health_bar)

func _update_health_bar(new_hp: int, max_hp: int) -> void:
    $HealthBar.value = new_hp

Emitting Signals

Call .emit() on the signal object. Arguments must match the signal declaration.

Emit without arguments
signal level_complete

func reach_exit() -> void:
    level_complete.emit()  # all connected callbacks fire
Emit with arguments
signal coin_collected(coin_value: int, position: Vector2)

func _on_body_entered(body: Node2D) -> void:
    if body.is_in_group("player"):
        coin_collected.emit(value, global_position)
        queue_free()

Built-in Signals

Godot nodes come with pre-defined signals. Connect them in code or via the editor.

Common built-in signals
# Timer
$Timer.timeout.connect(_on_timeout)

# Area2D collision
$Hitbox.body_entered.connect(_on_body_entered)
$Hitbox.area_entered.connect(_on_area_entered)

# Button
$PlayButton.pressed.connect(_on_play_pressed)

# AnimationPlayer
$Anim.animation_finished.connect(_on_anim_finished)
Node lifecycle signals
# Fires when the node enters the scene tree
tree_entered.connect(_on_added_to_tree)

# Fires when the node is about to be removed
tree_exiting.connect(_on_leaving_tree)

# Fires when all children are ready
ready.connect(_on_ready_signal)

Passing Extra Data with bind()

Use .bind() to attach extra arguments to a callback. Bound args are appended after signal args.

Bind an ID to a button callback
for i in range(3):
    var btn: Button = $HBox.get_child(i)
    btn.pressed.connect(_on_slot_pressed.bind(i))

func _on_slot_pressed(slot_index: int) -> void:
    equip_item(slot_index)  # 0, 1, or 2
Bind a resource reference
signal upgrade_selected

func show_upgrades(upgrades: Array[Resource]) -> void:
    for upgrade in upgrades:
        var btn := Button.new()
        btn.text = upgrade.name
        btn.pressed.connect(_pick_upgrade.bind(upgrade))
        $Panel.add_child(btn)

func _pick_upgrade(upgrade: Resource) -> void:
    apply_upgrade(upgrade)

One-shot and Deferred Connections

CONNECT_ONE_SHOT auto-disconnects after the first emit. CONNECT_DEFERRED delays the call to the end of the frame.

One-shot connection
# Callback fires exactly once, then auto-disconnects
$Door.opened.connect(_on_door_first_open, CONNECT_ONE_SHOT)

func _on_door_first_open() -> void:
    unlock_achievement("explorer")
Deferred connection
# Callback runs at end of frame (safe for tree modifications)
$Enemy.died.connect(_on_enemy_died, CONNECT_DEFERRED)

func _on_enemy_died() -> void:
    # Safe to remove nodes here — deferred until frame end
    $Enemy.queue_free()
Combine flags
# One-shot AND deferred
$Trigger.body_entered.connect(
    _on_cutscene_trigger,
    CONNECT_ONE_SHOT | CONNECT_DEFERRED
)

Await Signals

Use await to pause execution until a signal fires. Great for sequential game logic.

Await a timer
func flash_damage() -> void:
    modulate = Color.RED
    await get_tree().create_timer(0.15).timeout
    modulate = Color.WHITE
Await a custom signal
func play_intro() -> void:
    $AnimPlayer.play("intro_cutscene")
    await $AnimPlayer.animation_finished
    start_gameplay()
Await in a sequence
func death_sequence() -> void:
    $Anim.play("death")
    await $Anim.animation_finished
    await get_tree().create_timer(1.0).timeout
    emit_signal("respawn_requested")

Each await pauses the current function. Other nodes keep running normally.

Disconnecting Signals

Disconnect to prevent callbacks from firing. Always check is_connected() first to avoid errors.

Basic disconnect
func _exit_tree() -> void:
    if $Timer.timeout.is_connected(_on_timeout):
        $Timer.timeout.disconnect(_on_timeout)
Disconnect a bound callable
var _attack_cb: Callable

func _ready() -> void:
    _attack_cb = _on_attack_pressed.bind(weapon_id)
    $AttackBtn.pressed.connect(_attack_cb)

func _exit_tree() -> void:
    if $AttackBtn.pressed.is_connected(_attack_cb):
        $AttackBtn.pressed.disconnect(_attack_cb)

When using bind(), store the Callable so you can disconnect the exact same reference later.

Signal Bus Pattern (Autoload)

A global autoload script that holds signals. Nodes emit and connect through the bus without direct references to each other.

Create the signal bus (events.gd autoload)
# events.gd — add as Autoload named "Events"
extends Node

signal enemy_died(enemy: Node2D, xp: int)
signal coin_collected(value: int)
signal game_over
signal score_changed(new_score: int)
Emit from any script
# enemy.gd
func die() -> void:
    Events.enemy_died.emit(self, xp_reward)
    queue_free()
Listen from any script
# hud.gd
func _ready() -> void:
    Events.score_changed.connect(_on_score_changed)
    Events.game_over.connect(_on_game_over)

func _on_score_changed(new_score: int) -> void:
    $ScoreLabel.text = str(new_score)

The signal bus decouples systems. The enemy does not need a reference to the HUD.

Signals with Groups

Groups let you broadcast to multiple nodes at once. Combine with signals for event-driven group communication.

Call a method on all group members
# All enemies take damage from a bomb
get_tree().call_group("enemies", "take_damage", 50)

# All coins play a collect animation
get_tree().call_group("collectibles", "play_collect")
Notify group via signal bus
# In events.gd autoload:
signal freeze_all_enemies

# In game_manager.gd:
func activate_freeze_power() -> void:
    Events.freeze_all_enemies.emit()

# In enemy.gd:
func _ready() -> void:
    Events.freeze_all_enemies.connect(_on_freeze)

func _on_freeze() -> void:
    $AnimPlayer.pause()
    set_physics_process(false)

Common Signal Pitfalls

Quick fixes for the most frequent signal bugs in Godot 4.

Double-fire: connected twice
# BUG: reconnecting in _ready on re-enter
func _ready() -> void:
    $Timer.timeout.connect(_on_timeout)  # fires twice!

# FIX: guard with is_connected()
func _ready() -> void:
    if not $Timer.timeout.is_connected(_on_timeout):
        $Timer.timeout.connect(_on_timeout)
Freed node still connected
# Emitter fires but receiver was queue_free'd
# Godot handles this silently — no crash, but no callback either

# FIX: disconnect before freeing, or use one-shot
func _exit_tree() -> void:
    Events.wave_complete.disconnect(_on_wave_complete)
Wrong argument count
signal hit(damage: int, attacker: Node2D)

# BUG: callback signature doesn't match
func _on_hit(damage: int) -> void:  # missing attacker!
    pass

# FIX: match the signal's parameter list
func _on_hit(damage: int, attacker: Node2D) -> void:
    knockback(attacker.global_position)

Godot 4 catches argument mismatches at parse time. If you see "too few arguments", check the signal declaration.

Learn GDScript in Depth
GDScript Tweens & Polish Practice →GDScript Collisions Practice →
Warm-up1 / 2

Can you write this from memory?

Connect the $SpawnTimer node's timeout signal to a method called _on_spawn_timer_timeout.

See Also
Node Operations →Input Handling →Arrays & Dictionaries →

Start Practicing GDScript

Free daily exercises with spaced repetition. No credit card required.

← Back to GDScript Syntax Practice
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.