Godot 4 のシグナル
Godot 4 では、シグナルは第一級の Signal 値であり、メソッド参照は Callable です。すべての場面でオブジェクト構文(button.pressed.connect(_on_pressed))を使用してください。これにより、パーサーがスペルミスのハンドラや、self に対するスペルミスのシグナルを拒否します。型付けされた他のノードに対するスペルミスのシグナルは、unsafe_property_access がエラーに設定されている場合にキャッチされます(godot-4-api-pitfalls スキルを参照)。文字列形式(connect("pressed", ...)、emit_signal("died"))は実行時にのみチェックされます。
宣言、発行、接続
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")
ハンドラは、シグナルが発行する引数を正確に受け入れなければなりません。不一致はパースエラーではありません。発行時に Godot は Error calling from signal 'x' to callable: '...': Method expected 0 argument(s), but called with 1. をログに記録し、ハンドラをスキップします。
bind() と unbind()
bind(a, b)は、シグナル自身の引数の後に追加の引数を追加します。pressedのハンドラが 1 つの値でバインドされている場合、1 つのパラメータを受け取ります。health_changed(old, new)が 1 つの値でバインドされている場合、old, new, boundの順序で 3 つのパラメータを受け取ります。unbind(n)は、最後のn個のシグナル引数を削除するため、引数なしの関数でも引数を送信するシグナルを処理できます。
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
切断、重複、ワンショット接続
disconnect()には、接続されたものと等しい Callable が必要です。再度構築された_handler.bind(1)は等しいと比較され、機能します。ラムダはそうではありません。変数に保持し、その変数を切断してください。- 同じ Callable を 2 回接続すると、
Signal 'x' is already connected to given callable ...とログに記録され、1 つの接続が維持されます。コードが 2 回実行される可能性がある場合(例えば、ツリーに再追加されるノードの_ready()内)、is_connected()でガードしてください。 connect(callable, CONNECT_ONE_SHOT)は、最初の発行後に切断します。CONNECT_DEFERREDは、アイドル時にハンドラを実行します。物理コールバック(body_enteredなど)が物理状態を変更する場合に、これ(またはset_deferred())を使用してください。直接変更すると、Can't change this state while flushing queries.とログに記録されます。- オブジェクトへの接続は、そのオブジェクトが解放されると削除されます。
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 は、シグナルが発火するまで関数を一時停止し、その引数を返します。引数がない場合は null、1 つの引数の場合はその値、2 つ以上の引数の場合は Array を返します。await を含む関数はコルーチンです。その結果を必要とする呼び出し元も await しなければなりません。そうしないと、すぐに続行されます。
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 はタイムアウトしません。シグナルが発行されない(またはその発信元が先に解放される)場合、関数はエラーなしでその場で停止します。フォールバックパスが必要な場合は、Timer またはワンショット接続を使用してください。
エディタ接続
ノードドックで作成された接続は、スクリプトではなくシーンファイルに保存されます。
[connection signal="pressed" from="StartButton" to="." method="_on_start_button_pressed"]
メソッドがスクリプトで名前変更または削除された場合でも、接続は .tscn に残ります。Godot 4.7.2 では、接続されたメソッドが存在しないシグナルを発行してもエラーは出力されないため、ボタンは単に動作を停止します。ハンドラの名前を変更した後は、method="old_name" を探してシーンを検索し、更新するか、ノードドックで再接続してください。スクリプトを自己完結させるべき場合はコードで接続し、デザイナーがシーンを配線する場合はエディタで接続してください。同じシグナルとメソッドに対して両方を行うことは避けてください。2 回目の接続は "already connected" とログに記録されます。
呼び下げ、シグナル上げ
ノードはその子ノードのメソッドを呼び出し(所有しており、存在することを認識している)、親や兄弟が反応するためのシグナルを発行します(誰がリスニングしているかを知るべきではありません)。親、または 2 つを構成するノードが接続を行います。
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
この親は Player を Node2D としてしか認識しないため、カスタムシグナルを文字列として指定しています。プレイヤースクリプトに class_name Player を付け、変数を Player 型として宣言し、player.health_changed.connect(_on_player_health_changed) と記述することで、パーサーが名前もチェックするようにできます。
多くの無関係なシーンが気にするイベント(プレイヤーの死亡、レベルの終了)については、シグナルのみを宣言する自動読み込みスクリプトをイベントバスとして使用するのが一般的なパターンです:Events.player_died.emit() および Events.player_died.connect(_on_player_died)。
置き換える Godot 3 の形式
| 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") または obj.method |