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 antworten zu können. Anstatt beispielsweise ständig eine Taste zu überprüfen, ob sie gedrückt wird, kann die Taste bei Betätigung ein Signal senden.

Bemerkung

Weitere Informationen zum Beobachtermuster findest Du 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 anderen 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. Du kannst das Godot-Icon für die Sprite-Textur oder ein anderes Bild, das Dir gefällt, verwenden.

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 Drop-Down Menü 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

Klick auf das „timeout()“ Signal und danach auf „Verbinden…“. Auf dem folgenden Fenster kannst du definieren wie das Signal verbunden werden soll:

../../_images/signals_connect_dialog_timer.png

Auf der linken Seite siehst Du die Nodes in Deiner Szene. Du kannst den Node auswählen, auf den das Signal „hören“ soll. Beachte, dass der Timer-Node rot ist. Dies ist kein Fehler, sondern 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.

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 Du verwenden möchten. 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 Node

func _on_Timer_timeout():
    pass # replace with function body
public class TimerExample : Node
{
    private 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 Node

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 : Node
{
    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 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;
    }
}

Benutzerdefinierte Signale

Du kannst auch Deine eigenen Signale in Godot deklarieren:

extends Node

signal my_signal
public class Main : Node
{
    [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 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));
    }
}

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.