Instanciation avec des signaux

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.

Exemple de tir :

Imaginez un personnage joueur qui peut tourner et tirer vers la souris. Chaque fois que le bouton de la souris est cliqué, nous créons une instance de la balle à l’emplacement du joueur. Voir Instancier pour plus de détails.

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;
    }
}

Cependant, si les balles sont ajoutées en tant qu’enfants du joueur, elles resteront « attachées » au joueur lors de sa rotation :

../../_images/signals_shoot1.gif

Au lieu de cela, nous avons besoin que les balles soient indépendantes du mouvement du joueur - une fois tirées, elles doivent continuer à voyager en ligne droite et le joueur ne peut plus les affecter. Au lieu d’être ajouté à l’arbre de la scène en tant qu’enfant du joueur, il est plus logique d’ajouter la balle en tant qu’enfant de la scène de jeu « principale », qui peut être le parent du joueur ou même plus haut dans l’arbre.

Vous pouvez le faire en ajoutant directement la balle à la scène principale :

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 solution consiste à utiliser un signal pour « émettre » les balles du joueur. Le joueur n’a alors pas besoin de « savoir » ce qu’il advient des balles après cela. Quel que soit le nœud connecté au signal, il peut « recevoir » les balles et prendre les mesures appropriées pour les générer.

Voici le code pour le joueur utilisant des signaux pour émettre la balle :

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());
    }
}

Dans la scène principale, nous connectons ensuite le signal du joueur (celui-ci apparaîtra dans l’onglet « Nœud »).

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);
}

Maintenant, les balles conserveront leur propre mouvement indépendamment de la rotation du joueur :

../../_images/signals_shoot2.gif