Godot 4 シグナルパターン — 完全ガイド

1. はじめに — Godot 4で何が変わったか

Godot 4ではシグナルの仕組みが大幅に刷新されました。Godot 3の文字列ベースの object.connect("signal_name", target, "method_name") 構文は廃止され、代わりにCallable(呼び出し可能オブジェクト)ベースのAPIが導入されました。型安全でリファクタリングしやすく、実行時ではなくコンパイル時にエラーを検出できます。

このガイドでは、Godot 4.4+のシグナルについてすべてを解説します:宣言、接続、切断、発火、await、そして実践的なパターンまで。

ポイント

Godot 4では、シグナルは第一級オブジェクトです。プロパティとしてアクセスし(例:button.pressed)、メソッドを呼び出します(.connect().emit().disconnect())。文字列ベースのAPIはもう不要です。

2. カスタムシグナルの宣言

スクリプトの先頭で signal キーワードを使ってシグナルを宣言します。ドキュメントやエディタの自動補完のために、パラメータ名と型を指定することもできます。

player.gd
# Simple signal with no parameters
signal game_over

# Signal with typed parameters
signal health_changed(new_health: int)

# Signal with multiple parameters
signal item_picked_up(item_name: String, quantity: int)

# Signal with no type hints (works, but typed is recommended)
signal something_happened(data)
ヒント

シグナルのパラメータには常に型ヒントを追加しましょう。エディタでの自動補完が有効になり、コードが自己文書化されます。型ヒント付きのシグナルは、ノードドックのシグナルタブでもパラメータ情報が表示されます。

3. シグナルの接続(新しい方法)

Godot 4で最大の変更点はシグナルの接続方法です。文字列を渡す代わりに、シグナルをプロパティとして使い、Callable(関数への参照)を引数に .connect() を呼び出します。

基本的な接続

GDScript
# Godot 3 (OLD — no longer works in Godot 4)
# button.connect("pressed", self, "_on_button_pressed")

# Godot 4 — callable-based connection
button.pressed.connect(_on_button_pressed)

func _on_button_pressed() -> void:
    print("Button was pressed!")

bind()による追加引数付き接続

.bind() を使ってシグナルと一緒に追加データを渡せます。複数のシグナルを同じメソッドに接続するときに便利です。

GDScript
# Pass extra data via bind()
buy_button.pressed.connect(_on_item_button.bind("sword"))
sell_button.pressed.connect(_on_item_button.bind("shield"))

func _on_item_button(item_id: String) -> void:
    print("Selected item: ", item_id)

ラムダ(インライン)接続

短いハンドラーの場合、ラムダ関数を直接使えます。シンプルなロジックを接続箇所の近くに保てます。

GDScript
# Lambda — great for one-liners
button.pressed.connect(func(): print("Button pressed!"))

# Lambda with parameters
health_changed.connect(func(hp: int): health_label.text = str(hp))

# Multi-line lambda
enemy.died.connect(func():
    score += 100
    score_label.text = "Score: %d" % score
    print("Enemy defeated!")
)
注意

ラムダ接続は無名関数への参照がないため、簡単に切断できません。後で切断が必要な場合は、Callableを変数に保存するか、名前付きメソッドを使いましょう。

エディタでの接続(ノードドック)

GodotエディタのUIからもシグナルを接続できます。ノードを選択し、ノードドック > シグナルタブを開いて、シグナルをダブルクリックし、接続先のノードとメソッドを選びます。エディタはスクリプトに _on_button_pressed() のようなメソッドを自動生成します。コードで .connect() を呼ぶのと同じ結果になります。

4. シグナルの切断

シグナルを受信したくなくなったら切断します。ノードが解放されたときやゲームステートが切り替わるときのエラー防止に重要です。

GDScript
# Disconnect a signal
button.pressed.disconnect(_on_button_pressed)

# Always check before disconnecting to avoid errors
if button.pressed.is_connected(_on_button_pressed):
    button.pressed.disconnect(_on_button_pressed)
ベストプラクティス

シーンをまたぐシグナル接続(Autoloadへの接続など)では、ノード解放時のダングリング参照を防ぐために、必ず _exit_tree() で切断しましょう:

GDScript
func _ready() -> void:
    EventBus.player_died.connect(_on_player_died)

func _exit_tree() -> void:
    if EventBus.player_died.is_connected(_on_player_died):
        EventBus.player_died.disconnect(_on_player_died)

5. シグナルの発火

Godot 4では、旧来の emit_signal() の代わりに .emit() を使います。シグナルはオブジェクトなので、直接メソッドを呼び出します。

player.gd
signal health_changed(new_health: int)
signal died

var health: int = 100

func take_damage(amount: int) -> void:
    health -= amount
    health_changed.emit(health)  # Emit with argument

    if health <= 0:
        died.emit()  # Emit with no arguments
メモ

旧来の emit_signal("signal_name") はGodot 4でも技術的には動作しますが、非推奨です。新しいコードでは常に signal_name.emit() を使いましょう。文字列ベースのバージョンは将来のGodotリリースで削除される可能性があります。

6. シグナルのawait

Godot 4では yield()await に置き換わりました。シグナルが発火するまで関数を一時停止できるので、カットシーン、チュートリアル、連続アニメーション、タイマーイベントに最適です。

GDScript
func play_cutscene() -> void:
    # Wait for a timer
    await get_tree().create_timer(2.0).timeout

    # Wait for an animation to finish
    animation_player.play("intro")
    await animation_player.animation_finished

    # Wait for player input (custom signal)
    dialogue_label.text = "Press any key to continue..."
    await player_pressed_continue

    # Continue execution after the signal fires
    print("Cutscene complete!")

awaitしたシグナルから値を取得

シグナルが引数を発火する場合、await はそれらを返します。引数が1つなら値がそのまま返り、複数なら配列が返ります。

GDScript
# Single parameter — returns the value directly
var final_health: int = await health_changed
print("Health is now: ", final_health)

# Multiple parameters — returns an array
var result = await item_picked_up
var item_name: String = result[0]
var quantity: int = result[1]

7. シグナル接続フラグ

Godotは .connect() の第2引数として接続フラグを提供し、接続の動作を変更できます。

CONNECT_ONE_SHOT

シグナルが1回発火すると接続が自動で解除されます。デスアニメーションや実績アンロックなど、1回限りのイベントに最適です。

GDScript
# Auto-disconnects after firing once
enemy.died.connect(_on_first_kill, CONNECT_ONE_SHOT)

func _on_first_kill() -> void:
    unlock_achievement("first_blood")

CONNECT_DEFERRED

接続されたメソッドは、即座にではなく現在のフレームの最後(アイドル時間中)に呼び出されます。シグナルハンドラーがシーンツリーを変更する場合に便利で、物理処理やシグナル処理中にシーンツリーを変更するのは安全ではないためです。

GDScript
# Called at end of frame — safe for scene tree changes
button.pressed.connect(_on_restart, CONNECT_DEFERRED)

func _on_restart() -> void:
    get_tree().reload_current_scene()

フラグの組み合わせ

GDScript
# One-shot AND deferred
trigger.body_entered.connect(_on_trigger, CONNECT_ONE_SHOT | CONNECT_DEFERRED)

8. よく使うパターン

EventBus(グローバルシグナルハブ)

EventBusパターンはAutoloadシングルトンを使ってノードを疎結合にします。どのノードも、他のノードへの直接参照なしにグローバルイベントを発火・受信できます。Godotで最も強力なパターンの一つです。

event_bus.gd (Autoload)
extends Node

# Define all global signals in one place
signal player_died
signal score_changed(new_score: int)
signal level_completed(level_id: int)
signal item_collected(item_name: String)
signal settings_changed
player.gd (emitter)
func die() -> void:
    # Any script can emit global signals
    EventBus.player_died.emit()
hud.gd (listener)
func _ready() -> void:
    # Any script can listen to global signals
    EventBus.score_changed.connect(_on_score_changed)
    EventBus.player_died.connect(_on_player_died)

func _on_score_changed(new_score: int) -> void:
    score_label.text = "Score: %d" % new_score

func _on_player_died() -> void:
    game_over_screen.show()
Autoloadの設定方法

プロジェクト > プロジェクト設定 > 自動読み込みで、event_bus.gd スクリプトを追加し、名前を EventBus にします。自動的にグローバルアクセス可能なシングルトンになります。

シグナルリレー(親子間通信)

親ノードが子ノードのシグナルをリッスンし、情報をリレーまたは集約します。子ノードは互いの存在を知る必要がありません。

inventory.gd
signal inventory_updated

func _ready() -> void:
    # Connect to all slot children
    for slot in get_children():
        if slot.has_signal("item_changed"):
            slot.item_changed.connect(_on_slot_changed)

func _on_slot_changed() -> void:
    inventory_updated.emit()

Signal + Awaitによる順次ゲームフロー

level_manager.gd
func run_level() -> void:
    spawn_enemies()
    await EventBus.all_enemies_defeated

    show_treasure_chest()
    await EventBus.chest_opened

    play_exit_animation()
    await get_tree().create_timer(1.5).timeout

    load_next_level()

スポーンしたノードの動的接続

enemy_spawner.gd
func spawn_enemy(pos: Vector2) -> void:
    var enemy = enemy_scene.instantiate()
    enemy.position = pos

    # Connect signals before adding to scene tree
    enemy.died.connect(_on_enemy_died.bind(enemy))
    enemy.health_changed.connect(_on_enemy_health_changed)

    add_child(enemy)

func _on_enemy_died(enemy: Node) -> void:
    enemies_alive -= 1
    enemy.queue_free()

9. 移行チートシート(Godot 3 → 4)

Godot 3プロジェクトの移植時のクイックリファレンスとしてこの表をブックマークしてください。

操作 Godot 3 Godot 4
接続 connect("sig", obj, "method") sig.connect(method)
切断 disconnect("sig", obj, "method") sig.disconnect(method)
発火 emit_signal("sig", args) sig.emit(args)
接続確認 is_connected("sig", obj, "method") sig.is_connected(method)
シグナル待機 yield(obj, "sig") await obj.sig
追加引数 connect("sig", obj, "method", [data]) sig.connect(method.bind(data))
一回限り connect("sig", obj, "method", [], CONNECT_ONESHOT) sig.connect(method, CONNECT_ONE_SHOT)

10. よくある間違い

1. 文字列ベースのconnect(Godot 3スタイル)を使う

GDScript
# WRONG — Godot 3 syntax, will not compile
button.connect("pressed", self, "_on_button_pressed")

# CORRECT — Godot 4 syntax
button.pressed.connect(_on_button_pressed)

2. .emit()の代わりにemit_signal()を使う

emit_signal() はまだ動作しますが、コンパイル時チェックをバイパスし、公式に非推奨です。.emit() を使いましょう。

GDScript
# AVOID — deprecated, no compile-time checks
emit_signal("health_changed", health)

# PREFER — type-safe, catches typos at compile time
health_changed.emit(health)

3. 解放されたノードへの接続

ノードのメソッドにシグナルを接続し、そのノードが queue_free() で解放されると、次のシグナル発火時にクラッシュします。解決策:

  • _exit_tree() で切断する
  • 一回限りのイベントには CONNECT_ONE_SHOT を使う
  • 発火前に is_instance_valid(target) で確認する
GDScript
# Safe emission pattern
for connection in my_signal.get_connections():
    if is_instance_valid(connection["callable"].get_object()):
        pass  # Connection is still valid
my_signal.emit()  # Godot handles invalid connections gracefully in 4.x

4. シーンをまたぐシグナルの切断忘れ

Autoloadシグナルへの接続はシーン変更を越えて持続します。_ready() で接続して切断しなければ、シーンがリロードされたときにエラーや予期しない動作が発生します。

GDScript
# WRONG — never disconnects, leaks across scene changes
func _ready() -> void:
    EventBus.score_changed.connect(_on_score_changed)

# CORRECT — clean disconnect
func _ready() -> void:
    EventBus.score_changed.connect(_on_score_changed)

func _exit_tree() -> void:
    EventBus.score_changed.disconnect(_on_score_changed)

5. 同じシグナルを2回接続する

_ready() が複数回呼ばれる場合(ノードの再親設定など)、同じメソッドを2回接続してしまうことがあります。ハンドラーは発火ごとに2回実行されてしまいます。

GDScript
# Guard against double-connection
func _ready() -> void:
    if not EventBus.score_changed.is_connected(_on_score_changed):
        EventBus.score_changed.connect(_on_score_changed)

AIにシグナル設計を任せてみませんか?

Godot MCP ProはClaudeなどのAIアシスタントをGodotエディタに直接接続します。プロジェクト全体のシグナルフローの接続・切断・監査・可視化を自動で行えます。

analyze_signal_flow find_signal_connections connect_signal disconnect_signal get_signals
Godot MCP Pro を入手 — $15