Signaux dans Godot 4
Dans Godot 4, un signal est une valeur Signal de première classe et une référence de méthode est une Callable. Utilisez la syntaxe objet (button.pressed.connect(_on_pressed)) partout. L'analyseur rejette alors un gestionnaire mal orthographié, et un signal mal orthographié sur self ; un signal mal orthographié sur un autre nœud typé est détecté lorsque unsafe_property_access est réglé sur Erreur (voir la compétence godot-4-api-pitfalls). Les formes textuelles (connect("pressed", ...), emit_signal("died")) ne sont vérifiées qu'à l'exécution.
Déclarer, émettre, connecter
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")
Le gestionnaire doit accepter exactement les arguments émis par le signal. Une incompatibilité n'est pas une erreur d'analyse ; au moment de l'émission, Godot affiche Erreur lors de l'appel du signal 'x' à callable : '...': La méthode attendait 0 argument(s), mais a été appelée avec 1. et ignore le gestionnaire.
bind() et unbind()
bind(a, b)ajoute des arguments supplémentaires après les propres arguments du signal. Un gestionnaire pourpressedlié avec une valeur prend un paramètre ; pourhealth_changed(old, new)lié avec une valeur, il en prend trois, dans l'ordreold, new, lié.unbind(n)supprime lesnderniers arguments du signal, permettant à une fonction sans argument de gérer un signal qui en envoie certains.
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
Déconnecter, doublons et connexions uniques
disconnect()nécessite une Callable égale à celle connectée._handler.bind(1)reconstruit de manière égale et fonctionne. Une lambda ne le fait pas : conservez-la dans une variable et déconnectez cette variable.- Connecter la même Callable deux fois affiche
Le signal 'x' est déjà connecté à la callable donnée ...et conserve une connexion. Protégez-vous avecis_connected()lorsque le code peut s'exécuter deux fois, par exemple dans_ready()d'un nœud qui est réajouté à l'arbre. connect(callable, CONNECT_ONE_SHOT)se déconnecte après la première émission.CONNECT_DEFERREDexécute le gestionnaire au temps d'inactivité. Utilisez-le (ouset_deferred()) lorsqu'un callback physique tel quebody_enteredmodifie l'état physique ; le faire directement afficheImpossible de modifier cet état pendant le vidage des requêtes.- Les connexions à un objet sont supprimées lorsque cet objet est libéré.
extends Area2D
@onready var shape: CollisionShape2D = $CollisionShape2D
var _on_first_touch := func(body: Node2D) -> void: print(body.name, " a touché en premier")
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, " est entré")
func _close_after_entry(_body: Node2D) -> void:
shape.disabled = true
func stop_listening() -> void:
body_entered.disconnect(_on_body_entered)
await
await signal met la fonction en pause jusqu'à ce que le signal se déclenche et renvoie ses arguments : null pour aucun argument, la valeur pour un, un Array pour deux ou plus. Une fonction qui contient await est une coroutine ; les appelants qui ont besoin de son résultat doivent aussi await la fonction, sinon ils continuent immédiatement.
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("Porte ouverte : ", opened_by)
await play_intro()
print("Introduction terminée")
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 n'expire jamais. Si le signal n'est jamais émis (ou son émetteur est libéré en premier), la fonction s'arrête simplement là, sans erreur. Utilisez un Timer ou une connexion unique lorsqu'un chemin de repli est nécessaire.
Connexions de l'éditeur
Les connexions effectuées dans le dock Nœuds sont enregistrées dans le fichier de scène, pas dans le script :
[connection signal="pressed" from="StartButton" to="." method="_on_start_button_pressed"]
Si la méthode est renommée ou supprimée dans le script, la connexion reste dans le fichier .tscn. Dans Godot 4.7.2, émettre un signal dont la méthode connectée n'existe plus n'affiche aucune erreur, donc le bouton cesse simplement de fonctionner. Après avoir renommé un gestionnaire, recherchez dans les scènes method="ancien_nom" et mettez-les à jour, ou reconnectez dans le dock Nœuds. Connectez dans le code lorsque le script doit être autonome ; connectez dans l'éditeur lorsque les concepteurs câblent des scènes. Évitez de faire les deux pour le même signal et la même méthode : la seconde connexion affiche "déjà connecté".
Appeler vers le bas, signaler vers le haut
Un nœud appelle des méthodes sur ses enfants (il les possède et sait qu'ils existent) et émet des signaux auxquels son parent ou ses frères peuvent réagir (il ne doit pas savoir qui écoute). Le parent, ou le nœud qui compose les deux, effectue la connexion :
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
Ce parent ne connaît le Player que comme un Node2D, il nomme donc le signal personnalisé comme une chaîne. Donnez au script du joueur un class_name Player, tapez la variable comme Player et écrivez player.health_changed.connect(_on_player_health_changed) pour que l'analyseur vérifie aussi le nom.
Pour les événements dont de nombreuses scènes non liées se soucient (joueur mort, niveau terminé), un modèle courant est un script autoload qui ne déclare que des signaux, utilisé comme bus d'événements : Events.player_died.emit() et Events.player_died.connect(_on_player_died).
Formes Godot 3 à remplacer
| 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") ou obj.method |