Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

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 ein Button 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 führt zu weniger Kopplung 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 Godots Version des Observer-Patterns. Sie können hier mehr darüber erfahren: https://gameprogrammingpatterns.com/observer.html

Wir werden nun ein Signal verwenden, um unser Godot-Icon aus dem letzten Teil (Auf Spielereingaben hören) zu bewegen und durch Drücken eines Buttons anzuhalten.

Einrichten einer Szene

Um einen Button zu unserem Spiel hinzuzufügen, erstellen wir eine neue Hauptszene, die sowohl einen Button als auch die Szene sprite_2d.tscn enthält, die wir in der Lektion Erstellen eines ersten Skripts erstellt haben.

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

../../_images/signals_01_new_scene.webp

Klicken Sie im Szenendock auf den „2D-Szene“-Button. Dadurch wird ein Node2D als unser Root hinzugefügt.

../../_images/signals_02_2d_scene.webp

Klicken Sie im Dateisystem-Dock auf die Datei sprite_2d.tscn, die Sie zuvor gespeichert haben, und ziehen Sie sie auf den Node2D, um sie zu instanziieren.

../../_images/signals_03_dragging_scene.png

Wir möchten einen weiteren Node als Nachbar-Node des Sprite2D hinzufügen. Dazu klicken Sie mit der rechten Maustaste auf Node2D und wählen „Child-Node hinzufügen“.

../../_images/signals_04_add_child_node.webp

Suchen Sie den Button-Node und fügen Sie ihn hinzu.

../../_images/signals_05_add_button.webp

Der Node ist standardmäßig klein. Klicken und ziehen Sie auf den unteren rechten Griff des Buttons im Viewport, um die Größe zu ändern.

../../_images/signals_06_drag_button.png

Wenn Sie die Griffe nicht sehen, vergewissern Sie sich, dass das Auswahl-Tool in der Toolbar aktiviert ist.

../../_images/signals_07_select_tool.webp

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

Sie können auch ein Label auf den Button setzen, indem Sie seine Texteigenschaft im Inspektor bearbeiten. Geben Sie Bewegung ein/ausschalten ein.

../../_images/signals_08_toggle_motion_text.webp

Ihr Szenenbaum und Viewport sollten wie folgt aussehen.

../../_images/signals_09_scene_setup.png

Speichern Sie Ihre neu erstellte Szene unter dem Namen node_2d.tscn, falls Sie das nicht schon getan haben. Sie können sie dann mit F6 (Cmd + R unter macOS) starten. Im Moment ist der Button sichtbar, aber es wird nichts passieren, wenn Sie ihn drücken.

Verbinden von Signalen im Editor

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

Sie können Signale im Node-Dock verbinden. Wählen Sie den Button-Node aus und klicken Sie auf der rechten Seite des Editors auf den Tab "Node" neben dem Inspektor.

../../_images/signals_10_node_dock.webp

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

../../_images/signals_11_pressed_signals.webp

Doppelklicken Sie auf das „pressed“ Signal, um das Node-Verbindungsfenster zu öffnen.

../../_images/signals_12_node_connection.png

Dort können Sie das Signal mit dem Sprite2D-Node verbinden. Der Node benötigt eine Empfängermethode, eine Funktion, die Godot aufruft, wenn der Button das Signal aussendet. Der Editor generiert eine für Sie. Konventionell nennen wir diese Callback-Methoden nach dem Schema "_on_node_name_signal_name". In diesem Fall 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 Nodes mit dazugehörigem Skript 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 Built-in-Funktion verbinden, der Callback-Funktion Argumente hinzufügen und Optionen festlegen. Sie können den Modus unten rechts im Fenster ein- und ausschalten, indem Sie auf den Erweitert-Button klicken.

Klicken Sie auf den „Verbinden“-Button, um die Signalverbindung abzuschließen und zum Arbeitsbereich „Skript“ zu wechseln. Sie sollten die neue Methode mit einem Verbindungs-Icon am linken Rand sehen.

../../_images/signals_13_signals_connection_icon.webp

Wenn Sie auf das Icon klicken, öffnet sich ein Fenster und zeigt Informationen über die Verbindung an. Dieses Feature ist nur verfügbar, wenn Nodes im Editor verbunden werden.

../../_images/signals_14_signals_connection_info.webp

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

Unser Sprite2D bewegt sich dank des Codes in der Funktion _process(). Godot bietet eine Methode, um die Verarbeitung ein- und auszuschalten: Node.set_process(). Eine andere Methode der Klasse Node, is_processing(), gibt true zurück, wenn die Leerlauf-Verarbeitung aktiv ist. Wir können das Schlüsselwort not verwenden, um den Wert zu invertieren.

func _on_button_pressed():
    set_process(not is_processing())

Diese Funktion schaltet die Ausführung und damit die Bewegung des Icons beim Drücken des Buttons entsprechend ein und aus.

Bevor wir das Spiel ausprobieren, müssen wir unsere _process()-Funktion vereinfachen, um den Node automatisch zu bewegen und nicht auf die Eingabe des Anwenders warten zu müssen. Ersetzen Sie sie durch den folgenden Code, der bereits in der vorletzten Lektion vorkam:

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

Ihr vollständiger sprite_2d.gd-Code sollte wie folgt aussehen.

extends Sprite2D

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())

Starten Sie die Szene und klicken Sie auf den Button 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 Node 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 (Ctrl + Cmd + 1 unter macOS) drücken.

Klicken Sie im Szenendock mit der rechten Maustaste auf den Sprite2D-Node und fügen Sie einen neuen Child-Node hinzu. Suchen Sie nach Timer und fügen Sie den entsprechenden Node hinzu. Ihre Szene sollte nun wie folgt aussehen.

../../_images/signals_15_scene_tree.png

Gehen Sie bei ausgewähltem Timer-Node zum Inspektor und aktivieren Sie die Eigenschaft Autostart.

../../_images/signals_18_timer_autostart.png

Klicken Sie auf das Skript-Icon neben Sprite2D, um zum Arbeitsbereich für die Skripterstellung zurückzukehren.

../../_images/signals_16_click_script.png

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

  1. Eine Referenz auf den Timer aus dem Sprite2D holen.

  2. Die Methode connect() auf das "timeout"-Signal des Timers aufrufen.

Bemerkung

Um eine Verbindung zu einem Signal mittels Code herzustellen, müssen Sie die Methode connect() des Signals aufrufen, auf das Sie hören wollen. In diesem Fall wollen wir auf das "timeout"-Signal des Timers hören.

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

Um die Referenz auf einen Node relativ zum aktuellen zu bekommen, verwenden wir die Methode Node.get_node(). Wir können die Referenz in einer Variablen speichern.

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

Die Funktion get_node() sieht sich die Child-Nodes des Sprite2Ds an und holt die Nodes anhand ihres Namens. Wenn Sie zum Beispiel den Timer-Node im Editor in "BlinkingTimer" umbenannt haben, müssen Sie den Aufruf in get_node("BlinkingTimer") ändern.

Wir können nun den Timer mit dem Sprite2D in der _ready() Funktion verbinden.

func _ready():
    var timer = get_node("Timer")
    timer.timeout.connect(_on_timer_timeout)

Die Zeile liest sich wie folgt: Wir verbinden das "timeout"-Signal des Timers mit dem Node, an den das Skript angehängt ist. Wenn der Timer timeout aussendet, wollen wir die Funktion _on_timer_timeout() aufrufen, die wir noch definieren müssen. Fügen wir sie am Ende unseres Skripts hinzu und benutzen sie, um die Sichtbarkeit unseres Sprites ein- und auszuschalten.

Bemerkung

Konventionell benennen wir diese Callback-Methoden in GDScript als "_on_node_name_signal_name" und in C# als "OnNodeNameSignalName". In diesem Fall wird es "_on_timer_timeout" für GDScript und OnTimerTimeout() für C# sein.

func _on_timer_timeout():
    visible = not visible

Die Eigenschaft visible ist ein boolean-Wert, der festlegt, ob ein Node sichtbar ist. Die Zeile visible = not visible wechselt den Wert zwischen ein und aus. Wenn visible auf true war, wird es auf false gesetzt und umgekehrt.

Wenn Sie die Szene jetzt ausführen, werden Sie sehen, dass das Sprite im Sekundentakt an und aus blinkt.

Vollständiges Skript

Das war's mit unserer kleinen bewegten und blinkenden Godot-Icon-Demo! Hier ist die komplette Datei sprite_2d.gd als Referenz.

extends Sprite2D

var speed = 400
var angular_speed = PI


func _ready():
    var timer = get_node("Timer")
    timer.timeout.connect(_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 die Lebenspunkte des Spielers auf Null gesunken sind, kann man ein Signal namens "died" (gestorben) oder "health_depleted" (Lebenspunkte verbraucht) definieren, das ausgesendet wird, sobald die Lebenspunkte 0 erreichen.

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 Built-in-Signale: Sie erscheinen im Node-Tab und man kann sie wie alle anderen anbinden.

../../_images/signals_17_custom_signal.png

Um ein Signal in Ihren Skripten auszusenden, rufen Sie emit() für das Signal auf.

func take_damage(amount):
    health -= amount
    if health <= 0:
        health_depleted.emit()

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)

var health = 10

Bemerkung

Die Signal-Argumente werden im Node-Dock des Editors angezeigt, und Godot kann sie verwenden, um Callback-Funktionen für Sie zu erzeugen. Allerdings können Sie immer noch eine beliebige Anzahl von Argumenten aussenden, wenn Sie Signale aussenden. Es liegt also an Ihnen, die richtigen Werte auszusenden.

Um Werte zusammen mit dem Signal auszusenden, fügen Sie diese als zusätzliche Argumente der Funktion emit() hinzu:

func take_damage(amount):
    var old_health = health
    health -= amount
    health_changed.emit(old_health, health)

Zusammenfassung

Jeder Node in Godot sendet Signale aus, wenn etwas Spezifisches passiert, beispielsweise beim Drücken eines Buttons. Andere Nodes 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 Node reagieren, der gerade die Spielwelt betritt oder verlässt, auf eine Kollision, darauf dass ein Charakter einen bestimmten Bereich betritt oder verlässt, darauf dass ein Element der Benutzeroberfläche seine Größe verändert und vieles mehr.

Beispielsweise sendet ein Area2D, das eine Münze darstellt, ein body_entered-Signal aus, wenn der Physik-Body des Spielers in ihre Kollisions-Shape eintritt, sodass Sie wissen, wann der Spieler sie eingesammelt hat.

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