Patrones de señales en Godot 4 — Guía completa

1. Introducción — Qué cambió en Godot 4

Godot 4 renovó por completo el funcionamiento de las señales. Si vienes de Godot 3, la vieja sintaxis basada en cadenas object.connect("signal_name", target, "method_name") ya no existe. En su lugar, Godot 4 utiliza una API basada en Callable que es segura respecto a tipos, cómoda para refactorizar y que detecta errores en tiempo de compilación en vez de en tiempo de ejecución.

Esta guía cubre todo lo que necesitas saber sobre las señales en Godot 4.4+: declararlas, conectarlas, desconectarlas, emitirlas, esperarlas (await) y los patrones más útiles del mundo real.

Idea clave

En Godot 4, las señales son objetos de primera clase. Accedes a ellas como propiedades (p. ej. button.pressed) y llamas a métodos sobre ellas (.connect(), .emit(), .disconnect()). Se acabó la API basada en cadenas.

2. Declarar señales personalizadas

Declara las señales al principio de tu script con la palabra clave signal. Opcionalmente puedes especificar nombres y tipos de parámetros, para documentación y para el autocompletado del editor.

player.gd
# Simple signal with no parameters
signal game_over

# Signal with typed parameters
signal health_changed(new_health: int)

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

# Signal with no type hints (works, but typed is recommended)
signal something_happened(data)
Consejo

Añade siempre anotaciones de tipo a los parámetros de las señales. Esto activa el autocompletado en el editor y hace que tu código se documente a sí mismo. Las señales con anotaciones de tipo también muestran la información de sus parámetros en la pestaña Signals del panel Node.

3. Conectar señales (la nueva forma)

El mayor cambio en Godot 4 es la manera de conectar señales. En lugar de pasar cadenas, usas la señal como una propiedad y llamas a .connect() con un Callable (una referencia a una función).

Conexión básica

GDScript
# Godot 3 (OLD — no longer works in Godot 4)
# button.connect("pressed", self, "_on_button_pressed")

# Godot 4 — callable-based connection
button.pressed.connect(_on_button_pressed)

func _on_button_pressed() -> void:
    print("Button was pressed!")

Conexión con argumentos adicionales mediante bind()

Usa .bind() para pasar datos adicionales junto con la señal. Es útil cuando conectas varias señales al mismo método.

GDScript
# Pass extra data via bind()
buy_button.pressed.connect(_on_item_button.bind("sword"))
sell_button.pressed.connect(_on_item_button.bind("shield"))

func _on_item_button(item_id: String) -> void:
    print("Selected item: ", item_id)

Conexiones con lambda (en línea)

Para manejadores cortos puedes usar directamente una función lambda. Así la lógica sencilla queda cerca del punto de conexión.

GDScript
# Lambda — great for one-liners
button.pressed.connect(func(): print("Button pressed!"))

# Lambda with parameters
health_changed.connect(func(hp: int): health_label.text = str(hp))

# Multi-line lambda
enemy.died.connect(func():
    score += 100
    score_label.text = "Score: %d" % score
    print("Enemy defeated!")
)
Advertencia

Las conexiones con lambda no se pueden desconectar fácilmente, porque no tienes una referencia a la función anónima. Si necesitas desconectar más tarde, guarda el Callable en una variable o usa en su lugar un método con nombre.

Conectar en el editor (panel Node)

También puedes conectar señales a través de la interfaz del editor de Godot. Selecciona un nodo, abre la pestaña Signals del panel Node, haz doble clic en una señal y elige el nodo y el método de destino. El editor generará automáticamente un método como _on_button_pressed() en tu script. Es idéntico a llamar a .connect() en el código — solo te ahorra escribir.

4. Desconectar señales

Desconecta una señal cuando ya no quieras recibirla. Esto es importante para evitar errores cuando se liberan nodos o cuando cambias de estado de juego.

GDScript
# Disconnect a signal
button.pressed.disconnect(_on_button_pressed)

# Always check before disconnecting to avoid errors
if button.pressed.is_connected(_on_button_pressed):
    button.pressed.disconnect(_on_button_pressed)
Buena práctica

En las conexiones de señales entre escenas (p. ej. a un Autoload), desconecta siempre en _exit_tree() para evitar referencias colgantes cuando se libera el nodo:

GDScript
func _ready() -> void:
    EventBus.player_died.connect(_on_player_died)

func _exit_tree() -> void:
    if EventBus.player_died.is_connected(_on_player_died):
        EventBus.player_died.disconnect(_on_player_died)

5. Emitir señales

En Godot 4 usas .emit() en lugar del viejo emit_signal(). La señal es un objeto, así que llamas al método directamente sobre ella.

player.gd
signal health_changed(new_health: int)
signal died

var health: int = 100

func take_damage(amount: int) -> void:
    health -= amount
    health_changed.emit(health)  # Emit with argument

    if health <= 0:
        died.emit()  # Emit with no arguments
Nota

El viejo emit_signal("signal_name") técnicamente sigue funcionando en Godot 4, pero está obsoleto. Prefiere siempre signal_name.emit() para el código nuevo. La variante basada en cadenas podría eliminarse en una futura versión de Godot.

6. Esperar señales (await)

Godot 4 reemplazó yield() por await. Esto te permite pausar una función hasta que se dispare una señal — perfecto para cinemáticas, tutoriales, animaciones secuenciales y eventos temporizados.

GDScript
func play_cutscene() -> void:
    # Wait for a timer
    await get_tree().create_timer(2.0).timeout

    # Wait for an animation to finish
    animation_player.play("intro")
    await animation_player.animation_finished

    # Wait for player input (custom signal)
    dialogue_label.text = "Press any key to continue..."
    await player_pressed_continue

    # Continue execution after the signal fires
    print("Cutscene complete!")

Obtener valores de señales esperadas con await

Si la señal emite argumentos, await los devuelve. Con un único argumento obtienes el valor directamente. Con varios argumentos obtienes un array.

GDScript
# Single parameter — returns the value directly
var final_health: int = await health_changed
print("Health is now: ", final_health)

# Multiple parameters — returns an array
var result = await item_picked_up
var item_name: String = result[0]
var quantity: int = result[1]

7. Flags de conexión de señales

Godot ofrece flags de conexión como segundo argumento de .connect(), con los que puedes modificar el comportamiento de la conexión.

CONNECT_ONE_SHOT

La conexión se elimina automáticamente después de que la señal se dispare una vez. Perfecto para eventos únicos como animaciones de muerte o el desbloqueo de logros.

GDScript
# Auto-disconnects after firing once
enemy.died.connect(_on_first_kill, CONNECT_ONE_SHOT)

func _on_first_kill() -> void:
    unlock_achievement("first_blood")

CONNECT_DEFERRED

El método conectado se llama al final del fotograma actual (durante el tiempo de inactividad) en lugar de inmediatamente. Es útil cuando el manejador de la señal modifica el árbol de escena, algo que no es seguro hacer durante el procesamiento de física o de señales.

GDScript
# Called at end of frame — safe for scene tree changes
button.pressed.connect(_on_restart, CONNECT_DEFERRED)

func _on_restart() -> void:
    get_tree().reload_current_scene()

Combinar flags

GDScript
# One-shot AND deferred
trigger.body_entered.connect(_on_trigger, CONNECT_ONE_SHOT | CONNECT_DEFERRED)

8. Patrones comunes

EventBus (hub global de señales)

El patrón EventBus usa un singleton Autoload para desacoplar nodos. Cualquier nodo puede emitir o escuchar eventos globales sin necesitar una referencia directa a otro nodo. Es uno de los patrones más potentes de Godot.

event_bus.gd (Autoload)
extends Node

# Define all global signals in one place
signal player_died
signal score_changed(new_score: int)
signal level_completed(level_id: int)
signal item_collected(item_name: String)
signal settings_changed
player.gd (emitter)
func die() -> void:
    # Any script can emit global signals
    EventBus.player_died.emit()
hud.gd (listener)
func _ready() -> void:
    # Any script can listen to global signals
    EventBus.score_changed.connect(_on_score_changed)
    EventBus.player_died.connect(_on_player_died)

func _on_score_changed(new_score: int) -> void:
    score_label.text = "Score: %d" % new_score

func _on_player_died() -> void:
    game_over_screen.show()
Configurar el Autoload

Ve a Proyecto > Ajustes del proyecto > Autoload, añade tu script event_bus.gd y ponle el nombre EventBus. Se convierte automáticamente en un singleton accesible de forma global.

Relé de señales (comunicación padre-hijo)

Un nodo padre escucha las señales de sus hijos y transmite o agrega información. Los hijos nunca necesitan conocerse entre sí.

inventory.gd
signal inventory_updated

func _ready() -> void:
    # Connect to all slot children
    for slot in get_children():
        if slot.has_signal("item_changed"):
            slot.item_changed.connect(_on_slot_changed)

func _on_slot_changed() -> void:
    inventory_updated.emit()

Signal + Await para un flujo de juego secuencial

level_manager.gd
func run_level() -> void:
    spawn_enemies()
    await EventBus.all_enemies_defeated

    show_treasure_chest()
    await EventBus.chest_opened

    play_exit_animation()
    await get_tree().create_timer(1.5).timeout

    load_next_level()

Conexiones dinámicas para nodos instanciados

enemy_spawner.gd
func spawn_enemy(pos: Vector2) -> void:
    var enemy = enemy_scene.instantiate()
    enemy.position = pos

    # Connect signals before adding to scene tree
    enemy.died.connect(_on_enemy_died.bind(enemy))
    enemy.health_changed.connect(_on_enemy_health_changed)

    add_child(enemy)

func _on_enemy_died(enemy: Node) -> void:
    enemies_alive -= 1
    enemy.queue_free()

9. Chuleta de migración (Godot 3 → 4)

Guarda esta tabla como referencia rápida al portar tu proyecto de Godot 3.

Operación Godot 3 Godot 4
Conectar connect("sig", obj, "method") sig.connect(method)
Desconectar disconnect("sig", obj, "method") sig.disconnect(method)
Emitir emit_signal("sig", args) sig.emit(args)
Comprobar conexión is_connected("sig", obj, "method") sig.is_connected(method)
Esperar una señal yield(obj, "sig") await obj.sig
Vincular argumentos adicionales connect("sig", obj, "method", [data]) sig.connect(method.bind(data))
One-shot connect("sig", obj, "method", [], CONNECT_ONESHOT) sig.connect(method, CONNECT_ONE_SHOT)

10. Errores comunes

1. Usar connect basado en cadenas (estilo Godot 3)

GDScript
# WRONG — Godot 3 syntax, will not compile
button.connect("pressed", self, "_on_button_pressed")

# CORRECT — Godot 4 syntax
button.pressed.connect(_on_button_pressed)

2. Usar emit_signal() en lugar de .emit()

Aunque emit_signal() todavía funciona, evita las comprobaciones en tiempo de compilación y está oficialmente obsoleto. Usa .emit().

GDScript
# AVOID — deprecated, no compile-time checks
emit_signal("health_changed", health)

# PREFER — type-safe, catches typos at compile time
health_changed.emit(health)

3. Conectar a un nodo liberado

Si conectas una señal a un método de un nodo y ese nodo se libera con queue_free(), la siguiente emisión de la señal provocará un fallo. Soluciones:

  • Desconectar en _exit_tree()
  • Usar CONNECT_ONE_SHOT para eventos únicos
  • Comprobar con is_instance_valid(target) antes de emitir
GDScript
# Safe emission pattern
for connection in my_signal.get_connections():
    if is_instance_valid(connection["callable"].get_object()):
        pass  # Connection is still valid
my_signal.emit()  # Godot handles invalid connections gracefully in 4.x

4. Olvidar desconectar señales entre escenas

Las conexiones a señales de Autoload persisten a través de los cambios de escena. Si conectas en _ready() pero nunca desconectas, tendrás errores o comportamientos inesperados cuando se recargue la escena.

GDScript
# WRONG — never disconnects, leaks across scene changes
func _ready() -> void:
    EventBus.score_changed.connect(_on_score_changed)

# CORRECT — clean disconnect
func _ready() -> void:
    EventBus.score_changed.connect(_on_score_changed)

func _exit_tree() -> void:
    EventBus.score_changed.disconnect(_on_score_changed)

5. Conectar la misma señal dos veces

Si _ready() se llama varias veces (p. ej. al reasignar el padre de un nodo), puedes conectar accidentalmente el mismo método dos veces. Entonces el manejador se disparará dos veces por cada emisión.

GDScript
# Guard against double-connection
func _ready() -> void:
    if not EventBus.score_changed.is_connected(_on_score_changed):
        EventBus.score_changed.connect(_on_score_changed)

¿Quieres que la IA gestione la arquitectura de tus señales?

Godot MCP Pro conecta asistentes de IA como Claude directamente con tu editor de Godot. Puede conectar, desconectar, auditar y visualizar los flujos de señales de todo tu proyecto — de forma automática.

analyze_signal_flow find_signal_connections connect_signal disconnect_signal get_signals
Consigue Godot MCP Pro — $15