Sinais no Godot 4
No Godot 4, um sinal é um valor Signal de primeira classe e uma referência a método é um Callable. Use a sintaxe de objeto (button.pressed.connect(_on_pressed)) em todos os lugares. O analisador então rejeita um handler com nome incorreto e um sinal com nome incorreto em self; um sinal com nome incorreto em outro nó tipado é detectado quando unsafe_property_access está definido como Erro (veja o skill godot-4-api-pitfalls). As formas com string (connect("pressed", ...), emit_signal("died")) são verificadas apenas em tempo de execução.
Declarar, emitir, conectar
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")
O handler deve aceitar exatamente os argumentos que o sinal emite. Uma incompatibilidade não é um erro de análise; no momento da emissão, o Godot registra Error calling from signal 'x' to callable: '...': Method expected 0 argument(s), but called with 1. e ignora o handler.
bind() e unbind()
bind(a, b)anexa argumentos extras após os próprios argumentos do sinal. Um handler parapressedvinculado com um valor recebe um parâmetro; parahealth_changed(old, new)vinculado com um valor, recebe três, na ordemold, new, bound.unbind(n)descarta os últimosnargumentos do sinal, para que uma função sem argumentos possa tratar um sinal que envia alguns.
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
Desconectar, duplicatas e conexões únicas
disconnect()precisa de um Callable igual ao que foi conectado._handler.bind(1)construído novamente é comparado como igual e funciona. Uma lambda não: mantenha-a em uma variável e desconecte essa variável.- Conectar o mesmo Callable duas vezes registra
Signal 'x' is already connected to given callable ...e mantém uma conexão. Proteja comis_connected()quando o código puder ser executado duas vezes, por exemplo, em_ready()de um nó que é readicionado à árvore. connect(callable, CONNECT_ONE_SHOT)desconecta após a primeira emissão.CONNECT_DEFERREDexecuta o handler em tempo ocioso. Use-o (ouset_deferred()) quando um callback de física comobody_enteredaltera o estado da física; fazê-lo diretamente registraCan't change this state while flushing queries.- Conexões a um objeto são removidas quando esse objeto é liberado.
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 pausa a função até que o sinal dispare e retorna seus argumentos: null para nenhum argumento, o valor para um, um Array para dois ou mais. Uma função que contém await é uma corrotina; chamadores que precisam de seu resultado devem await também, caso contrário, continuam imediatamente.
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 nunca expira. Se o sinal nunca for emitido (ou seu emissor for liberado primeiro), a função simplesmente para ali, sem erro. Use um Timer ou uma conexão única quando um caminho alternativo for necessário.
Conexões do editor
Conexões feitas no dock de Nós são salvas no arquivo da cena, não no script:
[connection signal="pressed" from="StartButton" to="." method="_on_start_button_pressed"]
Se o método for renomeado ou excluído no script, a conexão permanece no .tscn. No Godot 4.7.2, emitir um sinal cujo método conectado não existe não imprime erro, então o botão simplesmente para de funcionar. Após renomear um handler, procure nas cenas por method="old_name" e atualize-os, ou reconecte no dock de Nós. Conecte em código quando o script deve ser autocontido; conecte no editor quando designers montam cenas. Evite fazer ambos para o mesmo sinal e método: a segunda conexão registra "already connected".
Chame para baixo, sinalize para cima
Um nó chama métodos em seus filhos (ele os possui e sabe que existem) e emite sinais para seu pai ou irmãos reagirem (ele não deve saber quem está ouvindo). O pai, ou qualquer nó que compõe os dois, faz a conexão:
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
Este pai conhece Player apenas como um Node2D, então nomeia o sinal personalizado como uma string. Dê ao script do jogador um class_name Player, digite a variável como Player e escreva player.health_changed.connect(_on_player_health_changed) para que o analisador também verifique o nome.
Para eventos que muitas cenas não relacionadas se importam (jogador morreu, nível concluído), um padrão comum é um script autoload que apenas declara sinais, usado como um barramento de eventos: Events.player_died.emit() e Events.player_died.connect(_on_player_died).
Formas do Godot 3 a serem substituídas
| 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 |