Signale

Einführung

Signale sind Godots Version des Beobachtungsmusters. Sie ermöglichen es einem Node, eine Nachricht zu senden, Nachrichten anderer Nodes zu empfangen und auf diese zu antworten. Anstatt beispielsweise ständig zu überprüfen, ob eine Taste gedrückt wird, kann die Taste bei Betätigung ein Signal senden.

Bemerkung

Weitere Informationen zum Beobachter-Muster findet man hier: http://gameprogrammingpatterns.com/observer.html

Signale sind eine Möglichkeit, Ihre Spielobjekte von anderen Objekten zu lösen, was zu besser organisiertem und überschaubarem Code führt. Anstatt, dass Spielobjekte erwarten, dass andere Objekte immer vorhanden sind, können sie stattdessen Signale aussenden, sodass jedes andere verknüpfte Objekt darauf antworten kann.

Nachfolgend finden Sie einige Beispiele, wie Sie Signale in Ihren eigenen Projekten verwenden können.

Timer Beispiel

Um zu sehen, wie Signale funktionieren, benutzen wir einen Timer Node. Erstelle eine neue Szene mit einem Node und zwei Unterelementen: einem "Timer" und einem Sprite. Benenne Node2D im Szenendock zu TimerExample um.

Als Textur für die Sprite können Sie das Godot Icon, oder ein anderen Bild das Ihnen gefällt, verwenden. Sie können das tun, in dem Sie im Textur Attribut des Sprites den Punkt Load im Aufklappmenü auswählen. Fügen Sie dem Wurzel-Node ein leeres Skript hinzu.

Der Szenenbaum sollte so aussehen:

../../_images/signals_node_setup.png

Aktivieren Sie in den Eigenschaften des Timer-Nodes das Kontrollkästchen "An" neben Autostart. Dadurch wird der Timer automatisch gestartet, wenn Sie die Szene starten. Sie können die Wartezeit auf 1 Sekunde belassen.

Neben der Registerkarte "Inspektor" befindet sich eine Registerkarte mit der Bezeichnung "Node". Klicken Sie auf diese Registerkarte, um alle Signale anzuzeigen, die der ausgewählte Node ausgeben kann. Im Falle des Timer-Nodes handelt es sich um den "Timeout". Dieses Signal wird immer dann ausgegeben, wenn der Timer "0" erreicht.

../../_images/signals_node_tab_timer.png

Klicken Sie auf das Signal "timeout()" und klicken danach unten im Signalpanel auf "Verbinden...". Im folgenden Fenster kann definiert werden, wie das Signal verbunden werden soll:

../../_images/signals_connect_dialog_timer.png

Auf der linken Seite sind die Nodes in Ihrer Szene. Dort können Sie den Node auswählen, auf den das Signal "hören" soll. Beachten Sie, dass der Timer-Node blau ist. Dies ist ein visueller Hinweis darauf, dass der Node das Signal aussendet. Wählen Sie den Wurzel-Node aus.

Warnung

Der Ziel-Node muss mit einem Skript verbunden sein oder es wird eine Fehlermeldung angezeigt.

Wenn Sie das Fortgeschrittene-Menü anzeigen, werden Sie auf der rechten Seite sehen, dass man eine einfache Nummer von Argumenten von (möglichen) unterschiedlichen Typen anbinden kann. Das kann nützlich sein, wenn man mehr als ein Signal mit der selben Methode verbunden hat, da jede Signal-Verwendung in unterschiedlichen Werten für diese extra Aufruf-Argumente resultieren werden.

Am unteren Rand des Fensters befindet sich ein Feld mit der Bezeichnung "Methode im Node". Dies ist der Name der Funktion im Skript des Ziel-Nodes, die verwendet werden soll. Standardmäßig erstellt Godot diese Funktion mit der Namenskonvention _on_<node_name>_<signal_name>, aber Sie können sie wenn gewünscht auch ändern.

Klicken Sie auf "Verbinden" und Sie sehen, dass die Funktion im Skript erstellt wurde:

extends Node2D


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

Jetzt können wir den Platzhaltercode durch den Code ersetzen, den wir ausführen möchten, wenn das Signal empfangen wird. Lassen wir das Sprite blinken:

extends Node2D


func _on_Timer_timeout():
    # Note: the `$` operator is a shorthand for `get_node()`,
    # so `$Sprite` is equivalent to `get_node("Sprite")`.
    $Sprite.visible = !$Sprite.visible
public class TimerExample : Node2D
{
    public void _on_Timer_timeout()
    {
        var sprite = GetNode<Sprite>("Sprite");
        sprite.Visible = !sprite.Visible;
    }
}

Starten Sie die Szene und beobachten, wie das Sprite im Sekundentakt ein- und ausgeschaltet wird. Sie können die Wait Time -Eigenschaft des Timers verändern, um die Blinkfrequenz zu ändern.

Signale im Code verbinden

Sie können die Signalverbindung auch im Code und nicht mit dem Editor herstellen. Dies ist normalerweise erforderlich, wenn Sie Nodes über Code instanziierst. Daher können Sie den Editor nicht zum Herstellen der Verbindung verwenden.

Trennen Sie zunächst das Signal, indem Sie die Verbindung in der Registerkarte "Node" des Timers auswählen und dort auf Trennen klicken.

../../_images/signals_disconnect_timer.png

Um die Verbindung im Code herzustellen, können wir die Funktion connect verwenden. Wir legen die Verbindung in _ready()``an, sodass sie direkt beim Ausführen erstellt wird. Die Syntax der Funktion ist ``<source_node>.connect(<signal_name>, <target_node>, <target_function_name>). Hier ist der Code für unsere Timer-Verbindung:

extends Node2D


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


func _on_Timer_timeout():
    $Sprite.visible = !$Sprite.visible
public class TimerExample : Node2D
{
    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;
    }
}

Benutzerdefinierte Signale

Sie können auch Ihre eigenen Signale in Godot deklarieren:

extends Node2D


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

Nach der Deklaration erscheinen Ihre benutzerdefinierten Signale im Inspektor und können wie die eingebauten Signale eines Nodes verbunden werden.

Um ein Signal per Code auszusenden, verwende die Funktion emit_signal:

extends Node2D


signal my_signal


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

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

Ein Signal kann optional ein oder mehrere Argumente deklarieren. Spezifiziere die Argumentnamen zwischen den Klammern:

extends Node


signal my_signal(value, other_value)
public class Main : Node
{
    [Signal]
    public delegate void MySignal(bool value, int other_value);
}

Bemerkung

Die Signal-Argumente werden oben im Bearbeitungs-Node-Dock auftauchen, und Godot kann diese nutzen, um Aufruf-Funktionen für Sie zu generieren. Allerdings kann man immer noch jede beliebige Anzahl von Argumenten aufrufen, wenn man Signale aufruft. Es ist also in der Hand der Person, diese richtigen Werte aufzurufen.

Um Werte zu übergeben, füge sie als zweites Argument zur Funktion emit_signal hinzu:

extends Node


signal my_signal(value, other_value)


func _ready():
    emit_signal("my_signal", true, 42)
public class Main : Node
{
    [Signal]
    public delegate void MySignal(bool value, int other_value);

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

Fazit

Viele der in Godot integrierten Node-Typen liefern Signale, mit denen Sie Ereignisse erkennen können. Zum Beispiel ein Area2D würde eine Münze repräsentieren, die immer das Signal body_entered sendet, sobald die Spielerfigur die Kollisions-Form der Münze berührt. Damit wissen Sie, das der Spieler die Münze erreicht hat und einsammeln kann.

Im nächsten Abschnitt, Das erste Spiel, werden Sie ein komplettes Spiel erstellen, inklusive der Nutzung von Signalen, um die verschiedenen Spielkomponenten miteinander zu verbinden.