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.
Checking the stable version of the documentation...
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 .
In the Scene dock, click the button. This will add a Node2D as our root.
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.
We want to add another node as a sibling of the Sprite2D. To do so, right-click on Node2D and select .
Suchen Sie den Button-Node und fügen Sie ihn hinzu.
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.
Wenn Sie die Griffe nicht sehen, vergewissern Sie sich, dass das Auswahl-Tool in der Toolbar aktiviert ist.
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.
Ihr Szenenbaum und Viewport sollten wie folgt aussehen.
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.
Das Dock zeigt eine Liste aller verfügbaren Signale für das ausgewählte Node an.
Doppelklicken Sie auf das „pressed“ Signal, um das Node-Verbindungsfenster zu öffnen.
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.
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 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 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.
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.
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())
// We also specified this function name in PascalCase in the editor's connection window.
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
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
public override void _Process(double delta)
{
Rotation += _angularSpeed * (float)delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * (float)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())
using Godot;
public partial class MySprite2D : Sprite2D
{
private float _speed = 400;
private float _angularSpeed = Mathf.Pi;
public override void _Process(double delta)
{
Rotation += _angularSpeed * (float)delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * (float)delta;
}
// We also specified this function name in PascalCase in the editor's connection window.
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
}
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.
With the Timer node selected, go to the Inspector and enable the Autostart property.
Klicken Sie auf das Skript-Icon neben Sprite2D, um zum Arbeitsbereich für die Skripterstellung zurückzukehren.
Es müssen zwei Schritte ausgeführt werden, um die Nodes im Code zu verbinden:
Eine Referenz auf den Timer aus dem Sprite2D holen.
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")
public override void _Ready()
{
var timer = GetNode<Timer>("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)
public override void _Ready()
{
var timer = GetNode<Timer>("Timer");
timer.Timeout += OnTimerTimeout;
}
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
private void OnTimerTimeout()
{
Visible = !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
using Godot;
public partial class MySprite2D : Sprite2D
{
private float _speed = 400;
private float _angularSpeed = Mathf.Pi;
public override void _Ready()
{
var timer = GetNode<Timer>("Timer");
timer.Timeout += OnTimerTimeout;
}
public override void _Process(double delta)
{
Rotation += _angularSpeed * (float)delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * (float)delta;
}
// We also specified this function name in PascalCase in the editor's connection window.
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
private void OnTimerTimeout()
{
Visible = !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
using Godot;
public partial class MyNode2D : Node2D
{
[Signal]
public delegate void HealthDepletedEventHandler();
private int _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.
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()
public void TakeDamage(int amount)
{
_health -= amount;
if (_health <= 0)
{
EmitSignal(SignalName.HealthDepleted);
}
}
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
using Godot;
public partial class MyNode : Node
{
[Signal]
public delegate void HealthChangedEventHandler(int oldValue, int newValue);
private int _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)
public void TakeDamage(int amount)
{
int oldHealth = _health;
_health -= amount;
EmitSignal(SignalName.HealthChanged, oldHealth, _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.