Instanciando con señales

Signals provide a way to decouple game objects, allowing you to avoid forcing a fixed arrangement of nodes. One sign that a signal might be called for is when you find yourself using get_parent(). Referring directly to a node’s parent means that you can’t easily move that node to another location in the scene tree. This can be especially problematic when you are instancing objects at runtime and may want to place them in an arbitrary location in the running scene tree.

Below we’ll consider an example of such a situation: firing bullets.

Ejemplo de disparos

Consideremos un personaje que pueda rotar y disparar en dirección al ratón. Cada vez que el botón del ratón sea presionado, creamos una instancia de la bala en la ubicación del jugador. Ver Instanciar para más detalles.

We’ll use an Area2D for the bullet, which moves in a straight line at a given velocity:

extends Area2D

var velocity = Vector2.ZERO

func _physics_process(delta):
    position += velocity * delta
public class Bullet : Area2D
{
    Vector2 Velocity = new Vector2();

    public override void _PhysicsProcess(float delta)
    {
        Position += Velocity * delta;
    }
}

Sin embargo, si las balas son añadidas como hijas del jugador, entonces permanecerán «ligadas» al jugador a medida que gira:

../../_images/signals_shoot1.gif

En su lugar, necesitamos que las balas sean independientes del movimiento del jugador - una vez disparadas, deberían continuar viajando en línea recta y el jugador ya no puede afectarlas. En vez de ser añadidas al árbol de escenas como un hijo del jugador, tiene más sentido añadir la bala como un hijo de la escena de juego «principal», la cual puede ser el padre del jugador o incluso más arriba en el árbol.

Podrías hacer esto agregando la bala directamente a la escena principal:

var bullet_instance = Bullet.instance()
get_parent().add_child(bullet_instance)
Node bulletInstance = Bullet.Instance();
GetParent().AddChild(bulletInstance);

However, this will lead to a different problem. Now if you try to test your «Player» scene independently, it will crash on shooting, because there is no parent node to access. This makes it a lot harder to test your player code independently and also means that if you decide to change your main scene’s node structure, the player’s parent may no longer be the appropriate node to receive the bullets.

La solución a esto es usar una señal para «emitir» las balas desde el jugador. El jugador entonces no tiene necesidad de «saber» qué pasa con las balas después de eso - cualquier nodo que esté conectado a la señal puede «recibir» las balas y tomar la acción apropiada para generarlas.

Este es el código para el jugador usando señales para emitir la bala:

extends Sprite

signal shoot(bullet, direction, location)

var Bullet = preload("res://Bullet.tscn")

func _input(event):
    if event is InputEventMouseButton:
        if event.button_index == BUTTON_LEFT and event.pressed:
            emit_signal("shoot", Bullet, rotation, position)

func _process(delta):
    look_at(get_global_mouse_position())
public class Player : Sprite
{
    [Signal]
    delegate void Shoot(PackedScene bullet, Vector2 direction, Vector2 location);

    private PackedScene _bullet = GD.Load<PackedScene>("res://Bullet.tscn");

    public override void _Input(InputEvent event)
    {
        if (input is InputEventMouseButton mouseButton)
        {
            if (mouseButton.ButtonIndex == (int)ButtonList.Left && mouseButton.Pressed)
            {
                EmitSignal(nameof(Shoot), _bullet, Rotation, Position);
            }
        }
    }

    public override _Process(float delta)
    {
        LookAt(GetGlobalMousePosition());
    }
}

En la escena princial, contectaremos la señal del jugador(aparecerá en la pestaña «Nodo»).

func _on_Player_shoot(Bullet, direction, location):
    var b = Bullet.instance()
    add_child(b)
    b.rotation = direction
    b.position = location
    b.velocity = b.velocity.rotated(direction)
public void _on_Player_Shoot(PackedScene bullet, Vector2 direction, Vector2 location)
{
    var bulletInstance = (Bullet)bullet.Instance();
    AddChild(bulletInstance);
    bulletInstance.Rotation = direction;
    bulletInstance.Position = location;
    bulletInstance.Velocity = bulletInstance.Velocity.Rotated(direction);
}

Ahora las balas mantendrán su propio movimiento independiente de la rotación del jugador:

../../_images/signals_shoot2.gif