Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

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.

Wie Methoden (Callable) sind Signale seit Godot 4.0 ein erstklassiger Typ. Das bedeutet, dass Sie sie direkt als Methodenargumente weitergeben können, ohne sie als Zeichenfolgen übergeben zu müssen, was eine bessere automatische Vervollständigung ermöglicht und weniger fehleranfällig ist. Eine Liste dessen, was Sie direkt mit dem Signaltyp tun können, finden Sie in der Klassenreferenz Signal.

Siehe auch

Wie in der Einleitung erwähnt, sind Signale Godots Version des Beobachtermusters. Weitere Informationen hierzu finden Sie unter „Game Programming Patterns <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.

Bemerkung

Für dieses Projekt halten wir uns an die Godot-Namenskonvention.

  • GDScript: Klassen (Nodes) nutzen PascalCase, Variablen und Funktionen snake_case und Konstanten ALL_CAPS (Siehe GDScript Style Guide).

  • C#: Klassen, Exportvariablen und Methoden PascalCase, private Felder _camelCase, lokale Variablen und Parameter verwenden camelCase (Siehe C#-Styleguide). Beachten Sie die genaue Schreibweise, wenn Sie Signale einbinden möchten.

Einrichten einer Szene

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

Create a new scene by going to the menu Scene > New Scene.

../../_images/signals_01_new_scene.webp

In the Scene dock, click the 2D Scene button. This will add a Node2D as our root.

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

We want to add another node as a sibling of the Sprite2D. To do so, right-click on Node2D and select Add Child Node.

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

You can also write a label on the Button by editing its Text property in the Inspector. Enter Toggle motion.

../../_images/signals_08_toggle_motion_text.webp

Ihr Szenenbaum und Viewport sollten wie folgt aussehen.

../../_images/signals_09_scene_setup.webp

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.

You can connect signals in the Signals dock. Select the Button node and, on the right side of the editor, click on the tab named Signals next to the Inspector.

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

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

When connecting signals via the editor's Signals dock, you can use two modes. The simple one only allows you to connect to nodes that have a script attached to them and creates a new callback function on them.

../../_images/signals_advanced_connection_window.webp

The advanced view lets you connect to any node and any built-in function, add arguments to the callback, and set options. You can toggle the mode in the window's bottom-right by clicking the Advanced button.

Bemerkung

If you are using an external editor (such as VS Code), this automatic code generation might not work. In this case, you need to connect the signal via code as explained in the next section.

Click the Connect button to complete the signal connection and jump to the Script workspace. You should see the new method with a connection icon in the left margin.

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

Run the current scene by pressing F6 (Cmd + R on macOS), and click the button to see the sprite start and stop.

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.webp

With the Timer node selected, go to the Inspector and enable the Autostart property.

../../_images/signals_18_timer_autostart.webp

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

../../_images/signals_16_click_script.webp

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 Node2D-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.

Your signals work the same way as built-in ones: they appear in the Signals tab and you can connect to them like any other.

../../_images/signals_17_custom_signal.webp

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 Node2D

signal health_changed(old_value, new_value)

var health = 10

Bemerkung

The signal arguments show up in the editor's Signals dock, and Godot can use them to generate callback functions for you. However, you can still emit any number of arguments when you emit signals. So it's up to you to emit the correct values.

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.