Signale in Godot 4
In Godot 4 ist ein Signal ein erstklassiger Signal-Wert und ein Methodenverweis ein Callable. Verwenden Sie überall die Objektsyntax (button.pressed.connect(_on_pressed)). Der Parser erkennt dann einen falsch geschriebenen Handler und ein falsch geschriebenes Signal auf self; ein falsch geschriebenes Signal auf einem anderen typisierten Knoten wird abgefangen, wenn unsafe_property_access auf Error gesetzt ist (siehe Skill godot-4-api-pitfalls). Die String-Formen (connect("pressed", ...), emit_signal("died")) werden nur zur Laufzeit geprüft.
Deklarieren, emitten, verbinden
extends Node
signal health_changed(old_value: int, new_value: int)
signal died
var health := 100
func take_damage(amount: int) -> void:
var old := health
health = maxi(health - amount, 0)
health_changed.emit(old, health)
if health == 0:
died.emit()
extends Control
@onready var start_button: Button = %StartButton
@onready var quit_button: Button = %QuitButton
func _ready() -> void:
start_button.pressed.connect(_on_start_pressed)
quit_button.pressed.connect(get_tree().quit)
start_button.mouse_entered.connect(func() -> void: start_button.grab_focus())
func _on_start_pressed() -> void:
get_tree().change_scene_to_file("res://level_1.tscn")
Der Handler muss exakt die Argumente akzeptieren, die das Signal ausgibt. Ein Missmatch ist kein Parse-Fehler; zum Zeitpunkt des Emittens protokolliert Godot Error calling from signal 'x' to callable: '...': Method expected 0 argument(s), but called with 1. und überspringt den Handler.
bind() und unbind()
bind(a, b)hängt zusätzliche Argumente nach den eigenen Argumenten des Signals an. Ein Handler fürpressed, der mit einem Wert gebunden wird, nimmt einen Parameter entgegen; fürhealth_changed(old, new), der mit einem Wert gebunden wird, nimmt er drei Parameter entgegen, in der Reihenfolgeold, new, bound.unbind(n)entfernt die letztennSignal-Argumente, sodass eine parameterlose Funktion ein Signal verarbeiten kann, das einige sendet.
extends HBoxContainer
signal slot_chosen(index: int)
var slot_buttons: Array[Button] = []
func _ready() -> void:
for i in 4:
var button := Button.new()
button.text = "Slot %d" % (i + 1)
add_child(button)
slot_buttons.append(button)
button.pressed.connect(_on_slot_pressed.bind(i))
var timer := Timer.new()
add_child(timer)
timer.timeout.connect(_refresh)
slot_chosen.connect(_refresh.unbind(1))
timer.start(1.0)
func _on_slot_pressed(index: int) -> void:
slot_chosen.emit(index)
func _refresh() -> void:
for button in slot_buttons:
button.disabled = false
Trennen, Duplikate und Einmalverbindungen
disconnect()benötigt einen Callable, der mit dem verbundenen übereinstimmt._handler.bind(1), erneut erstellt, ist gleichwertig und funktioniert. Ein Lambda tut das nicht: bewahren Sie es in einer Variable auf und trennen Sie diese Variable.- Das zweimalige Verbinden desselben Callable protokolliert
Signal 'x' is already connected to given callable ...und behält eine Verbindung. Schützen Sie sich mitis_connected(), wenn Code zweimal ausgeführt werden kann, z. B. in_ready()eines Knotens, der dem Baum erneut hinzugefügt wird. connect(callable, CONNECT_ONE_SHOT)trennt die Verbindung nach der ersten Emission.CONNECT_DEFERREDführt den Handler zur Leerlaufzeit aus. Verwenden Sie es (oderset_deferred()), wenn ein Physik-Callback wiebody_enteredden Physikzustand ändert; dies direkt zu tun, protokolliertCan't change this state while flushing queries.- Verbindungen zu einem Objekt werden entfernt, wenn dieses Objekt freigegeben wird.
extends Area2D
@onready var shape: CollisionShape2D = $CollisionShape2D
var _on_first_touch := func(body: Node2D) -> void: print(body.name, " touched first")
func _ready() -> void:
body_entered.connect(_on_first_touch, CONNECT_ONE_SHOT)
body_entered.connect(_close_after_entry, CONNECT_DEFERRED)
if not body_entered.is_connected(_on_body_entered):
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node2D) -> void:
print(body.name, " entered")
func _close_after_entry(_body: Node2D) -> void:
shape.disabled = true
func stop_listening() -> void:
body_entered.disconnect(_on_body_entered)
await
await signal pausiert die Funktion, bis das Signal ausgelöst wird, und gibt seine Argumente zurück: null für keine Argumente, den Wert für eines, ein Array für zwei oder mehr. Eine Funktion, die await enthält, ist eine Koroutine; Aufrufer, die ihr Ergebnis benötigen, müssen ebenfalls await darauf warten, sonst fahren sie sofort fort.
extends Node2D
signal door_opened(door_name: String)
func _ready() -> void:
await get_tree().create_timer(0.5).timeout
var opened_by: String = await door_opened
print("Door opened: ", opened_by)
await play_intro()
print("Intro finished")
func play_intro() -> void:
var tween := create_tween()
tween.tween_property(self, "modulate:a", 1.0, 1.0).from(0.0)
await tween.finished
await timeout nie. Wenn das Signal niemals ausgelöst wird (oder sein Emitter zuerst freigegeben wird), stoppt die Funktion einfach dort, ohne Fehler. Verwenden Sie einen Timer oder eine Einmalverbindung, wenn ein Fallback-Pfad benötigt wird.
Editor-Verbindungen
Im Knoten-Dock erstellte Verbindungen werden in der Szenendatei gespeichert, nicht im Skript:
[connection signal="pressed" from="StartButton" to="." method="_on_start_button_pressed"]
Wenn die Methode im Skript umbenannt oder gelöscht wird, bleibt die Verbindung im .tscn. In Godot 4.7.2 gibt das Emitten eines Signals, dessen verbundene Methode nicht existiert, keinen Fehler aus, sodass der Button einfach nicht mehr funktioniert. Suchen Sie nach dem Umbenennen eines Handlers in den Szenen nach method="old_name" und aktualisieren Sie sie, oder verbinden Sie sie im Knoten-Dock neu. Verbinden Sie im Code, wenn das Skript in sich abgeschlossen sein soll; verbinden Sie im Editor, wenn Designer Szenen verdrahten. Vermeiden Sie beides für dasselbe Signal und dieselbe Methode: das zweite Verbinden protokolliert "already connected".
Call Down, Signal Up
Ein Knoten ruft Methoden auf seinen Kindern auf (er besitzt sie und weiß, dass sie existieren) und emittiert Signale, auf die sein Elternteil oder Geschwisterknoten reagieren können (er sollte nicht wissen, wer zuhört). Der Elternteil oder der Knoten, der die beiden zusammensetzt, führt die Verbindung durch:
extends Node2D
@onready var player: Node2D = $Player
@onready var health_bar: ProgressBar = $HUD/HealthBar
func _ready() -> void:
player.connect(&"health_changed", _on_player_health_changed)
func _on_player_health_changed(_old_value: int, new_value: int) -> void:
health_bar.value = new_value
Dieser Elternteil kennt Player nur als Node2D, daher benennt er das benutzerdefinierte Signal als String. Geben Sie dem Player-Skript einen class_name Player, typisieren Sie die Variable als Player und schreiben Sie player.health_changed.connect(_on_player_health_changed), damit der Parser auch den Namen prüft.
Für Ereignisse, die viele nicht verwandte Szenen betreffen (Spieler gestorben, Level beendet), ist ein häufiges Muster ein Autoload-Skript, das nur Signale deklariert und als Ereignisbus dient: Events.player_died.emit() und Events.player_died.connect(_on_player_died).
Zu ersetzende Godot-3-Formen
| Godot 3 | Godot 4 |
|---|---|
connect("pressed", self, "_on_pressed") | pressed.connect(_on_pressed) |
connect("pressed", self, "_on_pressed", [id]) | pressed.connect(_on_pressed.bind(id)) |
connect("timeout", self, "_on_timeout", [], CONNECT_ONESHOT) | timeout.connect(_on_timeout, CONNECT_ONE_SHOT) |
disconnect("pressed", self, "_on_pressed") | pressed.disconnect(_on_pressed) |
is_connected("pressed", self, "_on_pressed") | pressed.is_connected(_on_pressed) |
emit_signal("died") | died.emit() |
yield(obj, "signal_name") | await obj.signal_name |
funcref(obj, "method") | Callable(obj, "method") oder obj.method |