Signaux

Introduction

Les signaux sont la version de Godot du modèle d”observer. Ils permettent à un nœud d’envoyer un message que les autres nœuds peuvent écouter et auquel ils peuvent répondre. Par exemple, au lieu de vérifier en continu si un bouton est appuyé, le bouton peut émettre un signal lorsqu’il est appuyé.

Note

Vous pouvez en savoir plus sur le modèle d’observateur ici : http://gameprogrammingpatterns.com/observer.html

Les signaux sont un moyen de rendre vos objets de jeu indépendants, ce qui conduit à un code mieux organisé et plus facile à gérer. Au lieu de forcer les objets du jeu à s’attendre à ce que d’autres objets soient toujours présents, ils peuvent émettre des signaux auxquels tout objet intéressé peut s’abonner et répondre.

Vous trouverez ci-dessous quelques exemples d’utilisation des signaux dans vos propres projets.

Exemple de minuterie

Pour voir comment fonctionnent les signaux, essayons d’utiliser un noeud Timer. Créez une nouvelle scène avec un nœud et deux enfants : un Timer et un Sprite. Vous pouvez utiliser l’icône Godot pour la texture du Sprite, ou toute autre image que vous souhaitez. Attachez un script au nœud racine, mais n’y ajoutez pas encore de code.

L’arbre de la scène devrait ressembler à ceci :

../../_images/signals_node_setup.png

Dans les propriétés du nœud Timer, cochez la case « On » à côté de Autostart. Le Timer démarrera automatiquement lorsque vous exécuterez la scène. Vous pouvez laisser le Wait Time (temps d’attente) à 1 seconde.

À côté de l’onglet « Inspecteur » se trouve un onglet intitulé « Nœud ». Cliquez sur cet onglet et vous verrez tous les signaux que le nœud sélectionné peut émettre. Dans le cas du nœud Timer, celui qui nous intéresse est « timeout ». Ce signal est émis chaque fois que la minuterie atteint 0.

../../_images/signals_node_tab_timer.png

Cliquez sur le signal « timeout() » et cliquez sur « Connecter… ». Vous verrez la fenêtre suivante dans laquelle vous pouvez définir comment vous voulez connecter le signal :

../../_images/signals_connect_dialog_timer.png

Sur le côté gauche, vous verrez les nœuds de votre scène et pourrez sélectionner le nœud que vous souhaitez « écouter » pour le signal. Notez que le nœud Timer est rouge - ce n’est pas une erreur, mais une indication visuelle que c’est le nœud qui émet le signal. Sélectionnez le nœud racine.

Avertissement

Le nœud cible doit être associé à un script ou vous recevrez un message d’erreur.

Au bas de la fenêtre se trouve un champ intitulé « Méthode en Noeud ». Il s’agit du nom de la fonction que vous souhaitez utiliser dans le script du nœud cible. Par défaut, Godot créera cette fonction en utilisant la convention de dénomination _on_<nom_nœud>_<nom_signal> mais vous pouvez la modifier si vous le souhaitez.

Cliquez sur « Connecter » et vous verrez que la fonction a été créée dans le script :

extends Node

func _on_Timer_timeout():
    pass # replace with function body
public class TimerExample : Node
{
    private void _on_Timer_timeout()
    {
        // Replace with function body.
    }
}

Nous pouvons maintenant remplacer le code de l’espace réservé par le code que nous souhaitons exécuter à la réception du signal. Faisons clignoter le Sprite :

extends Node

func _on_Timer_timeout():
    $Sprite.visible = !$Sprite.visible
public class TimerExample : Node
{
    public void _on_Timer_timeout()
    {
        var sprite = GetNode<Sprite>("Sprite");
        sprite.Visible = !sprite.Visible;
    }
}

Exécutez la scène et vous verrez le Sprite clignoter à chaque seconde. Vous pouvez modifier la propriété Temps d’Attente du temporisateur pour modifier cela.

Connecter des signaux dans le code

Vous pouvez également établir la connexion du signal en code plutôt qu’avec l’éditeur. Cela est généralement nécessaire lorsque vous installez des nœuds via du code et vous ne pouvez donc pas utiliser l’éditeur pour établir la connexion.

Commencez par déconnecter le signal en sélectionnant la connexion dans l’onglet « Nœud » de la minuterie et en cliquant sur Déconnecter.

../../_images/signals_disconnect_timer.png

Pour établir la connexion en code, nous pouvons utiliser la fonction connecter. Nous allons la mettre dans _ready() afin que la connexion soit établie lors de l’exécution. La syntaxe de la fonction est ``<nœud_source>.connecter(<nom_signal>, <nœud_cible>, <nom_fonction_ cible>)`. Voici le code pour la connexion de notre minuterie :

extends Node

func _ready():
    $Timer.connect("timeout", self, "_on_Timer_timeout")

func _on_Timer_timeout():
    $Sprite.visible = !$Sprite.visible
public class TimerExample : Node
{
    public override void _Ready()
    {
        GetNode("Timer").Connect("timeout", this, nameof(_on_Timer_timeout));
    }

    public void _on_Timer_timeout()
    {
        var sprite = GetNode<Sprite>("Sprite");
        sprite.Visible = !sprite.Visible;
    }
}

Signaux personnalisés

Vous pouvez également déclarer vos propres signaux personnalisés dans Godot :

extends Node

signal my_signal
public class Main : Node
{
    [Signal]
    public delegate void MySignal();
}

Une fois déclarés, vos signaux personnalisés apparaîtront dans l’inspecteur et pourront être connectés de la même manière que les signaux intégrés d’un nœud.

Pour émettre un signal par le code, utilisez la fonction emit :

extends Node

signal my_signal

func _ready():
    emit_signal("my_signal")
public class Main : Node
{
    [Signal]
    public delegate void MySignal();

    public override void _Ready()
    {
        EmitSignal(nameof(MySignal));
    }
}

Exemple de tir :

Comme autre exemple d’utilisation du signal, considérons un personnage qui peut pivoter et tirer vers la souris. Chaque fois que vous cliquez sur le bouton de la souris, nous créons une instance de la balle à l’emplacement du joueur. Voir Instancier pour plus de détails.

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 :

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

Cependant, cela conduira à un problème différent. Maintenant, si vous essayez de tester votre scène « Player » de manière indépendante, elle se bloquera lors du tir, car il n’y a pas de nœud parent auquel accéder. Cela rend beaucoup plus difficile le test indépendant du code de votre joueur et signifie également que si vous décidez de modifier la structure des nœuds de votre scène principale, le parent du joueur risque de ne plus être le nœud approprié pour recevoir les balles.

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

Conclusion

De nombreux types de nœuds intégrés de Godot fournissent des signaux que vous pouvez utiliser pour détecter des événements. Par exemple, un Area2D représentant une pièce de monnaie émet un signal body_entered chaque fois que le corps physique du joueur entre en collision avec, ce qui vous permet de savoir quand le joueur la récupère.

Dans la section suivante Votre premier jeu, vous construirez un jeu complet comprenant plusieurs utilisations de signaux pour connecter différents composants du jeu.