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, deine 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 findest Du einige Beispiele, wie Du Signale in Deinen eigenen Projekten verwenden kannst.

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 kannst du das Godot Icon, oder ein anderen Bild das dir gefällt, verwenden. Das kannst du tun, in dem du im Textur Attribut des Sprites den Punkt Load im Aufklappmenü auswählst. Füge der Wurzelnode ein leeres Skript hinzu.

Der Szenenbaum sollte so aussehen:

../../_images/signals_node_setup.png

Aktiviere in den Eigenschaften des Timer-Nodes das Kontrollkästchen "An" neben Autostart. Dadurch wird der Timer automatisch gestartet, wenn Du die Szene startest. Du kannst die Wartezeit auf 1 Sekunde belassen.

Neben der Registerkarte "Inspektor" befindet sich eine Registerkarte mit der Bezeichnung "Node". Klicke 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

Klicke auf das Signal "timeout()" und klicke 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 Deiner Szene. Dort kannst du den Node auswählen, auf den das Signal "hören" soll. Beachte, dass der Timer-Node blau ist. Dies ist ein visueller Hinweis darauf, dass der Node das Signal aussendet. Wähle 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 verwendt werden soll. Standardmäßig erstellt Godot diese Funktion mit der Namenskonvention _on_<node_name>_<signal_name>, aber Du kannst sie ändern, wenn Du möchtest.

Klicke auf "Verbinden" und Du siehst, 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;
    }
}

Starte die Szene und beobachte, wie das Sprite im Sekundentakt ein- und ausgeschaltet wird. Du kannst die Wait Time -Eigenschaft des Timers verändern, um die Blinkfrequenz zu ändern.

Signale im Code verbinden

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

Trenne zunächst das Signal, indem Du die Verbindung in der Registerkarte "Node" des Timers auswählst und dort auf Trennen klickst.

../../_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

Du kannst auch Deine eigenen Signale in Godot deklarieren:

extends Node2D


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

Nach der Deklaration erscheinen Deine 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 von Godots eingebauten Node-Typen verfügen über Signale, mit denen Du Ereignissen kannst. 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 weißt Du, das der Spieler die Münze erreicht hat und einsammeln kann.

Im nächsten Abschnitt, Dein erstes Spiel, wirst du ein komplettes Spiel erstellen, inklusive der Nutzung von Signalen, um die verschiedenen Spielkomponenten miteinander zu verbinden.