Using signals

En esta lección, veremos las señales. Son mensajes que emiten los nodos cuando les sucede algo específico, como que se presiona un botón. Otros nodos pueden conectarse a esa señal y llamar a una función cuando ocurra el evento.

Es un mecanismo de delegación integrado en Godot que permite que un objeto del juego reaccione a un cambio en otro sin que se refieran entre sí. El uso de señales limita el acoplamiento y mantiene su código flexible.

Por ejemplo, puede tener una barra de vida en la pantalla que represente la salud del jugador. Cuando el jugador recibe daño o usa una poción curativa, desea que la barra refleje el cambio. Para hacerlo, en Godot, usarías señales.

Nota

Como se mencionó en la introducción, las señales son la versión de Godot del patrón observador. Puede obtener más información al respecto aquí: https://gameprogrammingpatterns.com/observer.htm

We will now use a signal to make our Godot icon from the previous lesson (Escuchando la entrada del jugador) move and stop by pressing a button.

Configuración de la escena

To add a button to our game, we will create a new "main" scene which will include both a button and the Sprite.tscn scene that we scripted in previous lessons.

Cree una nueva escena yendo al menú Escenas -> Nueva escena.

../../_images/signals_01_new_scene.png

En el panel Escenas, haga clic en el botón Escena 2D. Esto agregará un Node2D como nuestra raíz.

../../_images/signals_02_2d_scene.png

En el panel del Sistema de Archivos, haga clic y arrastre el archivo Sprite.tscn que guardó previamente en Node2D para crear una instancia.

../../_images/signals_03_dragging_scene.png

Queremos agregar otro nodo como hermano del Sprite. Para hacerlo, haga clic con el botón derecho en Node2D y seleccione Añadir Nodo Hijo.

../../_images/signals_04_add_child_node.png

Busque el tipo de nodo Botón y agréguelo.

../../_images/signals_05_add_button.png

El nodo es pequeño por defecto. Haga clic y arrastre el controlador inferior derecho del botón en el viewport para cambiar su tamaño.

../../_images/signals_06_drag_button.png

Si no ve los controladores, asegúrese de que la herramienta de selección esté activa en la barra de herramientas.

../../_images/signals_07_select_tool.png

Haz clic y arrastra el botón para moverlo más cerca del sprite.

También puede escribir una etiqueta en el Botón editando su propiedad Text en el Inspector.

../../_images/signals_08_toggle_motion_text.png

Tu árbol de escenas y el viewport deberían verse así.

../../_images/signals_09_scene_setup.png

Save your newly created scene. You can then run it with F6.

Conexión de una señal en el editor

Aquí, queremos conectar la señal "pressed" del Botón a nuestro Sprite, y queremos llamar a una nueva función que activará y desactivará su movimiento. Necesitamos tener un script adjunto al nodo Sprite, lo cual hicimos en la lección anterior.

Puede conectar señales en el panel Nodos. Seleccione el nodo Button y, en el lado derecho del editor, haga clic en la pestaña denominada "Nodos" junto al Inspector.

../../_images/signals_10_node_dock.png

El panel muestra una lista de señales disponibles en el nodo seleccionado.

../../_images/signals_11_pressed_signals.png

Haga doble clic en la señal "pressed" para abrir la ventana de conexión del nodo.

../../_images/signals_12_node_connection.png

Allí, puede conectar la señal al nodo Sprite. El nodo necesita un método receptor, una función que Godot llamará cuando el Button emita la señal. El editor genera uno para usted. Por convención, llamamos a estos métodos callback "_on_NodeName_signal_name". Aquí, será "_on_Button_pressed".

Nota

Al conectar señales a través del panel de Nodos del editor, puede usar dos modos. El simple solo le permite conectarse a nodos que tienen un script adjunto y crea una nueva función de devolución de llamada en ellos.

../../_images/signals_advanced_connection_window.png

La vista avanzada le permite conectarse a cualquier nodo y cualquier función integrada, agregar argumentos al callback, y establecer opciones. Puede alternar el modo en la parte inferior derecha de la ventana haciendo clic en el checkbox de selección.

Haga clic en el botón de conexión para completar la conexión de la señal y pasar al espacio de trabajo de Script. Debería ver el nuevo método con un icono de conexión en el margen izquierdo.

../../_images/signals_13_signals_connection_icon.png

Si hace clic en el icono, aparece una ventana que muestra información sobre la conexión. Esta función solo está disponible cuando se conectan nodos en el editor.

../../_images/signals_14_signals_connection_info.png

Reemplacemos la línea con la palabra clave pass con código que cambiará el movimiento del nodo.

Nuestro Sprite se mueve gracias al código en la función _process(). Godot proporciona un método para activar y desactivar el procesamiento: Node.set_process(). Otro método de la clase Node, is_processing(), retorna true si el procesamiento por defecto está activo. Podemos usar la palabra clave not para invertir el valor.

func _on_Button_pressed():
    set_process(not is_processing())

Esta función alternará el procesamiento y, sucesivamente, la moción del icono entre activa e inactiva tras apretar el botón.

Antes de probar el juego, debemos simplificar nuestra función _process() para mover el nodo automaticamente y no esperar un aporte del usuario. Remplazalo con el siguiente código, el cual vimos hace dos lecciones:

func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta

El código completo de``Sprite2D.gd`` debería verse como el siguiente.

extends Sprite

var speed = 400
var angular_speed = PI


func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta


func _on_Button_pressed():
    set_process(not is_processing())

Ejecuta la escena y haz clic en el botón para ver el sprite comenzar y parar.

Conexión de una señal a través de código

Puedes conectar señales a través de código en vez de usar el editor. Esto es necesario cuando creas nodos o representas escenas usando instancias dentro de un script.

Vamos a usar un nodo diferente aquí. Godot tiene un nodo Timer que es útil para implementar tiempos de espera para habilidades, recargas de armas, y más.

Regrese al espacio de trabajo 2D. Puede hacer clic en el texto "2D" en la parte superior de la ventana o presionar Ctrl + F1 (Alt + 1 en macOS).

In the Scene dock, right-click on the Sprite node and add a new child node. Search for Timer and add the corresponding node. Your scene should now look like this.

../../_images/signals_15_scene_tree.png

Con el nodo Timer seleccionado, ve al Inspector y revisa la propiedad Autostart.

../../_images/signals_18_timer_autostart.png

Haz clic en el icono de script al lado del Sprite para volver al espacio de trabajo de scripts.

../../_images/signals_16_click_script.png

Debemos hacer dos operaciones para conectar los nodos con código:

  1. Obtenga una referencia al Timer del Sprite.

  2. Ejecuta el método connect() del temporizador.

Nota

Para conectar una señal con código, debes ejecutar el método connect() del nodo al cual quieras escuchar. En este caso, queremos escuchar la señal "timeout" del temporizador.

We want to connect the signal when the scene is intantiated, and we can do that using the Node._ready() built-in function, which is called automatically by the engine when a node is fully instantiated.

Para obtener una referencia al nodo relativo al actual, usamos el método Node.get_node(). Podemos almacenar la referencia en una variable.

func _ready():
    var timer = get_node("Timer")

La función get_node() comprueba los descendientes del Sprite y obtiene los nodos por su nombre. Por ejemplo, si has renombrado el nodo temporizador a "BlinkingTimer" en el editor, deberías de cambiar la llamada a get_node("BlinkingTimer").

Ahora podemos conectar el temporizador al sprite en la función _ready().

func _ready():
    var timer = get_node("Timer")
    timer.connect("timeout", self, "_on_Timer_timeout")

La linea es interpretada de la siguiente manera: conectamos la señal "timeout" del temporizador al nodo al cual el script está adjunto (self). Cuando el temporizador emite "timeout", queremos ejecutar la función "_on_Timer_timeout", la cual tenemos que definir. Vamos a añadirla al final de nuestro script y usarla para alternar la visibilidad de nuestro sprite.

func _on_Timer_timeout():
    visible = not visible

La propiedad visible``es un boolean que controla la visibilidad de nuestro nodo. La línea ``visible = not visible``alterna el valor. Si ``visible es true, se vuelve false, y vice-versa.

Complete script

That's it for our little moving and blinking Godot icon demo! Here is the complete Sprite.gd file for reference.

extends Sprite

var speed = 400
var angular_speed = PI


func _ready():
    var timer = get_node("Timer")
    timer.connect("timeout", self, "_on_Timer_timeout")


func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta


func _on_Button_pressed():
    set_process(not is_processing())


func _on_Timer_timeout():
    visible = not visible

Señales personalizadas

Nota

This section is a reference on how to define and use your own signals, and does not build upon the project created in previous lessons.

Puedes definir señales personalizadas en un script. Digamos, por ejemplo, que quieres mostrar una pantalla de derrota cuando la vida del jugador alcanza 0. Para hacerlo, podrías definir una señal llamada "died" o "health_depleted" cuando su vida llegue a 0.

extends Node2D

signal health_depleted

var health = 10

Nota

Como las señales representan eventos que acaban de ocurrir, generalmente usamos un verbo en pasado como nombre.

Tus señales funcionan de la misma manera que las incorporadas: aparecen en la pestaña Nodos y puede conectarse a ellas como cualquier otra.

../../_images/signals_17_custom_signal.png

Para emitir una señal mediante código, utiliza la función emit_signal().

func take_damage(amount):
    health -= amount
    if health <= 0:
        emit_signal("health_depleted")

Una señal puede declarar opcionalmente uno o más argumentos. Especifique los nombres de los argumentos entre paréntesis:

extends Node

signal health_changed(old_value, new_value)

Nota

Estos argumentos se muestran en el panel de nodos del editor, y Godot puede usarlos para generar funciones de callback para usted. Sin embargo, usted puede seguir emitiendo cualquier número de argumentos cuando emite señales, así que depende de usted emitir los valores correctos.

Para emitir valores junto con la señal, agréguelos como argumentos extra a la función emit_signal:

func take_damage(amount):
    var old_health = health
    health -= amount
    emit_signal("health_changed", old_health, health)

Sumario

Cualquier nodo en Godot emite señales cuando les sucede algo específico, como cuando se presiona un botón. Otros nodos pueden conectarse a señales individuales y reaccionar a eventos seleccionados.

Las señales tienen muchos usos. Con ellos, puedes reaccionar a un nodo que entra o sale del mundo del juego, a una colisión, a un personaje que entra o sale de un área, a un elemento de la interfaz que cambia de tamaño y mucho más.

Por ejemplo, un Area2D que representa una moneda emite una señal body_entered cada vez que el cuerpo físico del jugador entra en su forma de colisión, permitiéndote saber cuándo el jugador la recogió.

En la siguiente sección, Tu primer juego 2D, crearás un juego 2D completo y pondrás en práctica todo lo que has aprendido hasta ahora.