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.
Klicken Sie im Szenendock auf den „2D-Szene“-Button. Dadurch wird ein Node2D als unser Root hinzugefügt.
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.
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“.
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.
Sie können auch ein Label auf den Button setzen, indem Sie seine Texteigenschaft im Inspektor bearbeiten. Geben Sie Bewegung ein/ausschalten
ein.
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.
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.
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
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.
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.
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())
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;
}
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
}
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.
Gehen Sie bei ausgewähltem Timer-Node zum Inspektor und aktivieren Sie die Eigenschaft Autostart.
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 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;
}
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.
Diese Signale arbeiten genau so wie Built-in-Signale: Sie erscheinen im Node-Tab und man kann sie wie alle anderen anbinden.
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 Node
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
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)
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.