Signale nutzen

In dieser Lektion werden wir uns mit Signalen beschäftigen. Das sind Nachrichten, die Nodes aussenden, wenn etwas Bestimmtes mit ihnen passiert, z. B. wenn eine Taste gedrückt wird. Andere Nodes können sich mit diesem Signal verbinden und eine Funktion aufrufen, wenn das Ereignis eintritt.

Signale sind ein in Godot integrierter Delegierungsmechanismus, der es einem Spielobjekt ermöglicht, auf eine Änderung in einem anderen zu reagieren, ohne dass sie sich gegenseitig referenzieren. Die Verwendung von Signalen schränkt die Kopplung ein und hält Ihren Code flexibel.

Sie könnten zum Beispiel einen Lebensbalken auf dem Bildschirm haben, der die Gesundheit des Spielers anzeigt. Wenn der Spieler Schaden erleidet oder einen Heiltrank benutzt, soll der Balken die Veränderung anzeigen. Dazu würden Sie in Godot Signale verwenden.

Bemerkung

Wie in der Einleitung erwähnt, sind Signale Godot's Version des Beobachtermusters. Sie können hier mehr darüber erfahren: https://gameprogrammingpatterns.com/observer.html

Wir werden nun ein Signal verwenden, um unser Godot-Symbol aus dem letzten Teil (Eingaben des Spielers abhören) zu bewegen und durch Drücken einer Taste anzuhalten.

Eine Szene einrichten

Um unserem Spiel einen Knopf hinzuzufügen, erstellen wir eine neue "main"-Szene, die sowohl einen Knopf als auch die Sprite.tscn Szene enthält, die wir in den vorherigen Lektionen geschrieben haben.

Erstellen Sie eine neue Szene, indem Sie das Menü Szene -> Neue Szene aufrufen.

../../_images/signals_01_new_scene.png

Klicken Sie im Szenendock auf die Schaltfläche 2D-Szene. Dadurch wird ein Node2D als Wurzel hinzugefügt.

../../_images/signals_02_2d_scene.png

Klicken Sie im Dateisystem-Dock auf die zuvor gespeicherte Datei Sprite.tscn und ziehen Sie sie auf die Node2D, um sie zu instanziieren.

../../_images/signals_03_dragging_scene.png

Wir wollen einen weiteren Knoten neben dem Sprite hinzufügen. Dazu klickst du mit der rechten Maustaste auf Node2D und wählst Node hier anhängen.

../../_images/signals_04_add_child_node.png

Suche nach dem Button Node-Typ und füge es hinzu.

../../_images/signals_05_add_button.png

Der Knoten ist standardmäßig klein. Klicken und ziehen Sie am unteren rechten Ziehpunkt der Schaltfläche, um die Größe zu ändern.

../../_images/signals_06_drag_button.png

Wenn Sie die Hinweise nicht sehen, vergewissern Sie sich, dass das Auswahlwerkzeug in der Werkzeugleiste aktiviert ist.

../../_images/signals_07_select_tool.png

Klicken und ziehen Sie den Knopf, um ihn näher an das Sprite zu bringen.

Sie können dem Knopf auch eine Beschriftung zuweisen, indem Sie ihre Text-Eigenschaft im Inspektor bearbeiten. Geben Sie "Toggle motion" ein.

../../_images/signals_08_toggle_motion_text.png

Dein Szenenbaum und Ansichtsfenster sollten wie folgt aussehen.

../../_images/signals_09_scene_setup.png

Speichern Sie Ihre neu erstellte Szene. Sie können sie dann mit F6 ausführen. Im Moment ist der Knopf sichtbar, aber es passiert nichts, wenn Sie ihn drücken.

Verbinden von Signalen im Editor

Hier wollen wir das Signal "pressed" des Buttons mit unserem Sprite verbinden und eine neue Funktion aufrufen, die seine Bewegung ein- und ausschaltet. Wir brauchen ein Skript, das an den Sprite-Knoten angehängt ist, wie wir es in der vorherigen Lektion getan haben.

Du kannst Signale im Node-Dock verbinden. Wähle den Button-Node aus und klicke auf der rechten Seite des Editors auf die Registerkarte "Node" neben dem Inspektor.

../../_images/signals_10_node_dock.png

Das Dock zeigt eine Liste aller verfügbaren Signale für das ausgewählte Node an.

../../_images/signals_11_pressed_signals.png

Doppelklick auf das „pressed“ Signal, um das Knotenverbindungsfenster zu öffnen.

../../_images/signals_12_node_connection.png

Dort kannst du das Signal mit dem Sprite-Knoten verbinden. Der Knoten benötigt eine Empfängermethode, eine Funktion, die Godot aufruft, wenn der Button das Signal sendet. Der Editor generiert die Funktion für dich. Der Konvention nach nennen wir diese Callback-Methoden "_on_NodeName_signal_name". Hier wird es "_on_Button_pressed" sein.

Bemerkung

Man kann zweierlei Modi verwenden, um Signale über das Node-Dock zu verbinden. Der einfache Modus erlaubt ausschließlich die Verbindung zu Knoten mit dazugehörigem Script und erstellt in diesem eine neue Callback-Funktion.

../../_images/signals_advanced_connection_window.png

In der erweiterten Ansicht können Sie sich mit jedem Node und jeder integrierten Funktion verbinden, dem Callback Argumente hinzufügen und Optionen festlegen. Sie können den Modus unten rechts im Fenster umschalten, indem Sie auf die Schaltfläche Erweitert klicken.

Klicken Sie auf die Schaltfläche „Verbinden“, um die Signalverbindung abzuschließen und zum Arbeitsbereich „Skript“ zu wechseln. Sie sollten die neue Methode mit einem Verbindungssymbol am linken Rand sehen.

../../_images/signals_13_signals_connection_icon.png

Wenn man auf das Symbol klickt öffnet sich ein Fenster und zeigt Informationen über die Verbindung an. Diese Eigenschaft ist nur verfügbar, wenn Knoten im Editor verbunden werden.

../../_images/signals_14_signals_connection_info.png

Lassen Sie uns die Zeile im Code mit dem pass Schlüsselwort durch Code ersetzen, welcher die Bewegung des Nodes umschaltet.

Unser Sprite lässt sich dank des Codes in der _process()-Funktion bewegen. Godot stellt eine Methode bereit, um den Ablauf zu aktivieren und zu deaktivieren: Node.set_process(). Eine weitere Methode der Node-Klasse, is_processing(), gibt true zurück, falls die Ausführung aktiv ist. Man kann diesen Wert über das Schlüsselwort not umdrehen.

func _on_Button_pressed():
    set_process(not is_processing())

Diese Funktion schaltet die Ausführung um und die Bewegung des Symbols beim Drücken der Knopfes entsprechend ein und aus.

Bevor man das Spiel ausprobiert, muss die _process()-Funktion vereinfacht werden, um den Knoten automatisch zu bewegen und nicht auf die Eingabe des Anwenders warten zu müssen. Daher ersetzen wir ihren Code durch den folgenden, der bereits in er vorletzten Lektion vor kam:

func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta

Der komplette Szenenbaum Sprite.gd sollte wie folgt aussehen.

extends Sprite

var speed = 400
var angular_speed = PI


func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta


func _on_Button_pressed():
    set_process(not is_processing())

Starte Sie die Szene und klicken Sie auf die Schaltfläche um das Sprite zum Laufen und Halten zu bringen.

Ein Signal über Code verbinden

Man kann Signale auch über den Code erzeugen anstelle den Editor zu verwenden. Dies ist notwendig, falls man Notes oder Szenen innerhalb eines Skriptes erzeugen möchte.

Wir verwenden an dieser Stelle einen anderen Node. Godot besitzt einen Knoten Timer, der hilfreich ist, um Skill-Cooldown-Zeiten, das Nachladen von Waffen oder anderes zu implementieren.

Gehen Sie zurück zum 2D-Arbeitsbereich. Sie können entweder auf den "2D"-Text oben im Fenster klicken oder Strg + F1 (Alt + 1 unter macOS) drücken.

Rechtsklicken sie im Szene-Panel das Sprite-Node und füge einen weiteren Knoten hinzu (Node hier anhängen). Suche sie nach Timer und ergänzt den entsprechenden Knoten. Im Anschluss sollte die Szene wie folgt aussehen.

../../_images/signals_15_scene_tree.png

Bei angewähltem Timer-Knoten lässt sich im Inspektor die Autostart-Eigenschaft anwählen.

../../_images/signals_18_timer_autostart.png

Drücken Sie auf das Skript-Symbol neben dem Sprite und springen Sie zurück in den Script-Arbeitsbereich.

../../_images/signals_16_click_script.png

Es müssen zwei Schritte ausgeführt werden, um die Knoten im Code zu verbinden:

  1. Ermittelt den Verweis auf den Timer des Sprites.

  2. Rufen Sie die connect()-Methode des Timers auf.

Bemerkung

Um das Signal im Quellcode anzubinden, ruft man die connect()-Methode des Knotens auf, dessen Signal man erwartet. In diesem Fall wird das "timeout"-Signal des Timers erwartet.

Wir wollen das Signal verbinden, wenn die Szene instanziiert wird und das können wir über die integrierte Funktion Node._ready(), welche automatisch von der Engine aufgerufen wird, wenn das Node vollständig instanziiert ist.

Um die Referenz auf einen Knoten zu erhalten, der sich in Relation zum aktuellen befindet, verwendet man die Methode Node.get_node(). Die Referenz kann in einer Variablen gespeichert werden.

func _ready():
    var timer = get_node("Timer")

Die Funktion get_node() ermittelt den Knoten an Hand des Namens der Sprite-Kinderknoten. Wenn man den Timer beispielsweise in "BlinkingTimer" umbenannt hat, muss man den Aufruf in get_node("BlinkingTimer") abändern.

Nun kann man den Timer in der _ready()-Funktion zum Sprite anbinden.

func _ready():
    var timer = get_node("Timer")
    timer.connect("timeout", self, "_on_Timer_timeout")

Die Zeile liest sich wie folgt: Das Signal "timeout" von Timer wird mit dem Knoten verbunden, dem das Script gehört (self). Sobald der Timer "timeout" sendet, soll die Funktion "_on_Timer_timeout" angestoßen werden, die gerade definiert wurde. Diese wird jetzt am Ende des Skriptes ergänzt, um die Sichtbarkeit des Sprites umschalten zu können.

func _on_Timer_timeout():
    visible = not visible

Die Eigenschaft visible ist ein boolean-Wert, der festlegt, ob ein Knoten sichtbar ist. Die Zeile visible = not visible wechselt zwischen sichtbar und unsichtbar hin und her. Wenn visible auf true war wird es auf false gestetzt und umgekehrt.

Wenn Sie die Szene jetzt ausführen, werden Sie sehen, dass das Sprite im Sekundentakt ein- und ausschaltet.

Vollständiges Skript

Das ist nun unsere kleine bewegende und blinkende Godot-icon-Demo. Hier ist die vollständige Sprite.gd-Datei als Referenz.

extends Sprite

var speed = 400
var angular_speed = PI


func _ready():
    var timer = get_node("Timer")
    timer.connect("timeout", self, "_on_Timer_timeout")


func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta


func _on_Button_pressed():
    set_process(not is_processing())


func _on_Timer_timeout():
    visible = not visible

Benutzerdefinierte Signale

Bemerkung

Dieser Abschnitt dient als Referenz für die Definition und Verwendung eigener Signale und baut nicht auf dem in den vorherigen Lektionen erstellten Projekt auf.

Man kann mehrere verschiedene Signale innerhalb eines Skriptes definieren. Möchte man beispielsweise einen "Game Over"-Bildschirm anzeigen, sobald der Gesundheitswert des Spielers auf Null gesunken ist, kann man ein Signal namens "died" oder "health_depleted" definieren, das angestoßen wird, sobald der Gesundheitswert auf Null gesetzt wurde.

extends Node2D

signal health_depleted

var health = 10

Bemerkung

Da sämtliche Signale Ereignisse repräsentieren, die gerade erst stattfanden, verwendet man üblicherweise die Vergangenheitsform des Verbes für deren Namen.

Diese Signale arbeiten genau so wie native: Sie erscheinen im Node-Tab und man kann sie wie alle anderen anbinden.

../../_images/signals_17_custom_signal.png

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

func take_damage(amount):
    health -= amount
    if health <= 0:
        emit_signal("health_depleted")

Ein Signal kann optional auch ein oder mehrere Argumente deklarieren. Geben Sie die Argumentnamen zwischen den Klammern an:

extends Node

signal health_changed(old_value, new_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 zusammen mit dem Signal auszugeben, fügen Sie sie als zusätzliche Argumente zur Funktion emit_signal() hinzu:

func take_damage(amount):
    var old_health = health
    health -= amount
    emit_signal("health_changed", old_health, health)

Zusammenfassung

Jeder Knoten in Godot sendet Signale, wenn etwas spezielles passiert, beispielsweise beim Drücken eines Buttons. Andere Knoten können sich mit definierten Signalen verbinden und so auf die ausgewählten Ereignisse reagieren.

Signale können vielfältig verwendet werden. Man kann auf einen Knoten reagieren, der gerade die Spielwelt betritt oder verlässt, auf eine Kollision, darauf dass eine Spielfigur einen bestimmten Bereich betritt oder verlässt, darauf dass ein Element seine Größe verändert und vieles mehr.

Beispielsweise gibt ein Area2D, das eine Münze darstellt, ein body_entered-Signal aus, wenn der physische Körper des Spielers in seine Kollisionsform eintritt, sodass Sie wissen, wann der Spieler ihn eingesammelt hat.

Im nächsten Abschnitt, Dein erstes 2D-Spiel, erstellen Sie ein komplettes 2D-Spiel und setzen alles, was Sie bisher gelernt haben, in die Praxis um.