Skip to main content
Signals are Godot’s implementation of the observer pattern, allowing nodes to communicate without directly referencing each other. This keeps your code flexible, decoupled, and easier to maintain.

What are Signals?

A Signal is a built-in type that represents an event emitted by an Object. When a signal is emitted, all connected Callables (functions) are triggered, allowing multiple objects to react to the same event without tight coupling.
Signals are Godot’s equivalent to events, delegates, or callbacks in other languages and frameworks.

Declaring Signals

You can declare custom signals in your scripts using the signal keyword:
extends Node

# Simple signal with no parameters
signal health_depleted

# Signal with parameters
signal health_changed(old_value, new_value)

# Signal with typed parameters (GDScript 2.0)
signal damage_taken(amount: int, damage_type: String)

# Signal with multiple parameters
signal item_collected(item_name: String, quantity: int, rarity: String)

Emitting Signals

Emit a signal using the emit() method or the emit_signal() function:
extends CharacterBody2D

signal health_changed(old_health, new_health)
signal died

var health = 100:
    set(value):
        var old_health = health
        health = clamp(value, 0, 100)
        health_changed.emit(old_health, health)
        
        if health <= 0:
            died.emit()

func take_damage(amount: int):
    health -= amount
When you emit a signal, all connected functions are called immediately in the order they were connected.

Connecting Signals

There are multiple ways to connect signals in Godot, each with specific use cases. This is the most type-safe approach:
func _ready():
    # Connect to a built-in signal
    var button = Button.new()
    button.pressed.connect(_on_button_pressed)
    
    # Connect to a custom signal
    var player = get_node("Player")
    player.health_changed.connect(_on_player_health_changed)
    player.died.connect(_on_player_died)

func _on_button_pressed():
    print("Button was pressed!")

func _on_player_health_changed(old_health, new_health):
    print("Health: ", old_health, " -> ", new_health)

func _on_player_died():
    print("Player died!")
    get_tree().reload_current_scene()

Method 2: Using Object.connect()

This legacy method uses string names:
func _ready():
    var button = Button.new()
    # Less type-safe - signal name is a string
    button.connect("pressed", _on_button_pressed)
The Object.connect() method is less safe because signal and method names are strings. Typos won’t be caught until runtime.

Method 3: In the Editor

You can connect signals visually in the Godot editor:
1

Select the Node

Click on the node that emits the signal in the Scene dock.
2

Open Node Panel

Click on the “Node” tab (next to Inspector) to see available signals.
3

Connect the Signal

Double-click a signal or click “Connect” to choose the target node and method.
4

Configure Connection

Choose the receiving node and method name, then click “Connect”.
Editor connections are saved in the scene file and automatically reconnected when the scene loads.

Binding Parameters

You can bind additional parameters to a signal connection using Callable.bind():
func _ready():
    # Create multiple enemies with different types
    var enemy1 = Enemy.new()
    var enemy2 = Enemy.new()
    
    # Bind different parameters to identify which enemy died
    enemy1.died.connect(_on_enemy_died.bind("Goblin", 10))
    enemy2.died.connect(_on_enemy_died.bind("Orc", 25))

# Signal parameters come first, bound parameters come after
func _on_enemy_died(enemy_type: String, score: int):
    print(enemy_type, " defeated! +", score, " points")
When you emit a signal with parameters and also use bind(), the emitted parameters come first, followed by the bound parameters.

Parameter Order Example

extends Node

signal attack_landed(target: Node, damage: int)

func _ready():
    var player = get_node("Player")
    # Bind weapon type
    player.attack_landed.connect(_on_attack.bind("Sword"))

func _on_attack(target: Node, damage: int, weapon: String):
    # target and damage come from emit()
    # weapon comes from bind()
    print("Hit ", target.name, " for ", damage, " with ", weapon)

Disconnecting Signals

You can disconnect signals when they’re no longer needed:
func _ready():
    var button = Button.new()
    button.pressed.connect(_on_button_pressed)
    
    # Later, disconnect the signal
    button.pressed.disconnect(_on_button_pressed)

func _on_button_pressed():
    print("This won't be called after disconnect")
When a node is freed, all its signal connections are automatically cleaned up. You usually don’t need to manually disconnect signals.

Connection Flags

You can modify signal behavior using connection flags:
func _ready():
    var button = Button.new()
    
    # One-shot: Disconnect automatically after first emission
    button.pressed.connect(_on_button_pressed, CONNECT_ONE_SHOT)
    
    # Deferred: Call the function at the end of the frame
    button.pressed.connect(_on_button_pressed, CONNECT_DEFERRED)
    
    # Reference counted: Allow multiple connections to the same callable
    button.pressed.connect(_on_button_pressed, CONNECT_REFERENCE_COUNTED)

Available Connection Flags

  • CONNECT_DEFERRED: Calls the connected method at the end of the current frame
  • CONNECT_PERSIST: The connection persists even if the target is freed (rarely used)
  • CONNECT_ONE_SHOT: Automatically disconnects after the first emission
  • CONNECT_REFERENCE_COUNTED: Allows connecting the same callable multiple times

Checking Connections

func _ready():
    var button = Button.new()
    
    # Check if signal is connected
    if not button.pressed.is_connected(_on_button_pressed):
        button.pressed.connect(_on_button_pressed)
    
    # Check if signal has any connections
    if button.pressed.has_connections():
        print("Button has listeners")
    
    # Get all connections
    var connections = button.pressed.get_connections()
    for connection in connections:
        print("Connected to: ", connection["callable"])

Built-in Node Signals

Godot nodes come with many useful built-in signals:
func _ready():
    # Tree signals
    tree_entered.connect(_on_tree_entered)
    tree_exiting.connect(_on_tree_exiting)
    tree_exited.connect(_on_tree_exited)
    
    # Child signals
    child_entered_tree.connect(_on_child_added)
    child_exiting_tree.connect(_on_child_removed)
    
    # Renamed signal
    renamed.connect(_on_node_renamed)

func _on_tree_entered():
    print("Node entered the scene tree")

func _on_tree_exiting():
    print("Node is about to leave the tree")

func _on_child_added(child: Node):
    print("Child added: ", child.name)

Practical Example: Health System

Here’s a complete example showing how to build a health system using signals:
# player.gd
extends CharacterBody2D

signal health_changed(current: int, maximum: int)
signal damage_taken(amount: int)
signal healed(amount: int)
signal died

var max_health = 100
var health = 100:
    set(value):
        var old_health = health
        health = clamp(value, 0, max_health)
        
        # Emit appropriate signals
        health_changed.emit(health, max_health)
        
        if health < old_health:
            damage_taken.emit(old_health - health)
        elif health > old_health:
            healed.emit(health - old_health)
        
        if health <= 0 and old_health > 0:
            died.emit()

func take_damage(amount: int):
    health -= amount

func heal(amount: int):
    health += amount
# ui_health_bar.gd
extends ProgressBar

func _ready():
    var player = get_node("/root/Main/Player")
    
    # Connect to player signals
    player.health_changed.connect(_on_player_health_changed)
    player.died.connect(_on_player_died)
    
    # Initialize the health bar
    max_value = player.max_health
    value = player.health

func _on_player_health_changed(current: int, maximum: int):
    max_value = maximum
    value = current
    
    # Change color based on health percentage
    var percent = float(current) / maximum
    if percent < 0.25:
        modulate = Color.RED
    elif percent < 0.5:
        modulate = Color.YELLOW
    else:
        modulate = Color.GREEN

func _on_player_died():
    # Animate health bar on death
    var tween = create_tween()
    tween.tween_property(self, "modulate:a", 0.0, 0.5)
# game_manager.gd
extends Node

func _ready():
    var player = get_node("/root/Main/Player")
    
    player.damage_taken.connect(_on_player_damaged)
    player.healed.connect(_on_player_healed)
    player.died.connect(_on_player_died)

func _on_player_damaged(amount: int):
    print("Player took ", amount, " damage")
    # Play damage sound, spawn particles, etc.

func _on_player_healed(amount: int):
    print("Player healed for ", amount)
    # Play heal effect

func _on_player_died():
    print("Game Over")
    await get_tree().create_timer(2.0).timeout
    get_tree().reload_current_scene()
Signals provide several advantages:
  1. Decoupling: The player doesn’t need to know about the UI or game manager
  2. Flexibility: Multiple systems can respond to the same event
  3. Maintainability: Easy to add/remove listeners without modifying the player
  4. Scene Independence: The player scene works standalone without dependencies

Signals with Groups

You can combine signals with node groups for powerful patterns:
# enemy.gd
extends CharacterBody2D

signal alerted

func _ready():
    add_to_group("enemies")
    alerted.connect(_on_alerted)

func alert():
    alerted.emit()

func _on_alerted():
    # This enemy is alerted
    print(name, " is alerted!")
    
    # Alert all other enemies in the group
    get_tree().call_group("enemies", "alert")

Advanced: Custom Signal Class

You can create signal objects dynamically:
var custom_signal = Signal()

func _ready():
    # Create a signal from an object and name
    var obj = Node.new()
    var sig = Signal(obj, "some_signal")
    
    # Check if object has a signal
    if obj.has_signal("some_signal"):
        sig.connect(_on_signal_emitted)

func _on_signal_emitted():
    print("Custom signal emitted")

Best Practices

1

Use Descriptive Signal Names

Name signals as events: died, health_changed, item_collected rather than on_death or health_update.
2

Include Relevant Parameters

Pass necessary information with signals. For damage_taken, include the amount and optionally the source.
3

Prefer Signal.connect() Over Object.connect()

Use the type-safe signal.connect() method to catch errors at compile time.
4

Clean Up When Needed

While automatic cleanup usually works, disconnect signals manually in specific cases like temporary connections.
5

Avoid Signal Chains

Don’t create long chains of signals calling signals. This makes debugging difficult.

Common Patterns

Button Click Handler

func _ready():
    $StartButton.pressed.connect(_on_start_pressed)
    $QuitButton.pressed.connect(_on_quit_pressed)

func _on_start_pressed():
    get_tree().change_scene_to_file("res://game.tscn")

func _on_quit_pressed():
    get_tree().quit()

Timer Completion

func _ready():
    var timer = Timer.new()
    add_child(timer)
    timer.timeout.connect(_on_timer_timeout)
    timer.start(5.0)

func _on_timer_timeout():
    print("5 seconds have passed")

Area Detection

func _ready():
    var area = $DetectionArea
    area.body_entered.connect(_on_body_entered)
    area.body_exited.connect(_on_body_exited)

func _on_body_entered(body: Node2D):
    if body.is_in_group("player"):
        print("Player detected!")

func _on_body_exited(body: Node2D):
    if body.is_in_group("player"):
        print("Player left detection range")

Next Steps

Nodes and Scenes

Learn about the fundamental building blocks of Godot

Scene Tree

Understand how the SceneTree manages your game

Build docs developers (and LLMs) love