Godot のシグナルとは:signal、connect、emit の使い方

コイン取得の例で Godot のシグナル機構を説明します。signal とは何か、connect の使い方、emit するタイミング、Main が Coin の collected シグナルを受け取ってスコアを更新する方法を整理します。

Godot の「シグナル」は、ノードが発する通知だと考えると分かりやすいです。

何かが起きた。関心のある相手がそれを処理する。

例:

  • ボタン:押された。
  • コイン:プレイヤーに取得された。
  • プレイヤー:体力が変わった。
  • 敵:死亡した。
  • タイマー:時間になった。

他のノードはこれらのシグナルに接続し、シグナルが発せられたときに関数を実行できます。送信側は受信側を直接探す必要がなく、ノード同士の依存を減らせます。

シグナルの 3 つの基本ステップ

シグナルの流れは通常 3 つの手順です。

  1. シグナルを宣言する。
  2. シグナルを接続する。
  3. シグナルを発する。

ドアベルにたとえると:

概念 たとえ
signal ドアベルを設置する
connect ドアベルを室内チャイムにつなぐ
emit ドアベルを押す
受信関数 中の人が聞いてドアを開ける

覚え方は次です。

1
2
3
signal  = 通知を定義する
connect = 受信者を決める
emit    = 通知を発する

Godot のシグナルは 2 種類

Godot のシグナルは、大きく内蔵シグナルと自作シグナルに分けられます。

内蔵シグナル

内蔵シグナルは、Godot のノードが最初から持っているシグナルです。

よくある例:

  • Button.pressed
  • Timer.timeout
  • Area2D.body_entered
  • Area2D.body_exited

コインノードが Area2D を継承しているなら、最初から body_entered シグナルを持っています。CharacterBody2D がコインの当たり判定領域に入ると、Godot が自動的にこのシグナルを発します。

自作シグナル

自作シグナルは自分で宣言します。

たとえばコインが取得されたことを表すなら:

1
signal collected

この collected は Godot の内蔵シグナルではなく、自分で定義した通知タイプです。

コイン例の 2 層のシグナル

簡単なコインスクリプトは次のように書けます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
extends Area2D

signal collected


func _ready() -> void:
    body_entered.connect(_on_body_entered)


func _on_body_entered(body: Node2D) -> void:
    if body.name != "Player":
        return

    collected.emit()
    queue_free()

ここでは実際に 2 層のシグナルを使っています。

第 1 層:body_entered

このコード:

1
body_entered.connect(_on_body_entered)

意味は次です。

物理オブジェクトが Coin の Area2D に入ったら、_on_body_entered() を呼ぶ。

流れ:

1
2
3
4
5
Player がコインの当たり判定領域に入る
Area2D が body_entered を発する
_on_body_entered(body) が実行される

渡される body は、コイン領域に入ったノードです。Player が入ったなら:

1
body == Player

第 2 層:collected

このコード:

1
signal collected

はシグナル宣言です。Coin が collected という通知を発せるようになります。

次に:

1
collected.emit()

これは次の通知を実際に発するという意味です。

このコインは取得された。

ただし、シグナルを発したからといって、必ず誰かが受け取るとは限りません。

どのノードも collected に接続していなければ、シグナルは発せられますが追加の処理は起きません。コインが消えるのは、次の行があるからです。

1
queue_free()

collected.emit() が自動的にコインを消しているわけではありません。

完全な通信の流れ

プレイヤーがコインに触れた後の流れは次です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Player が Coin に入る
Coin の Area2D が body_entered を発する
Coin が _on_body_entered() を実行する
入ったノードが Player だと確認する
Coin が collected を発する
Main が collected を受け取る
Main がスコアを増やす
Coin が queue_free() を実行する
コインが消える

ここには 2 つの送信者があります。

1
2
Area2D → body_entered を発する
Coin   → collected を発する

body_entered は Coin に「何かが領域に入った」と伝えます。

collected は Main に「このコインが取得された」と伝えます。

Main にコインのシグナルを受け取らせる

シーン構造が次だとします。

1
2
3
4
5
Main  Node2D
├─ Player
├─ Coin
└─ CanvasLayer
   └─ ScoreLabel

シグナルはエディタでもコードでも接続できます。

方法 1:Godot エディタで接続する

main.tscn を開きます。

まず、シーンツリーで次を選択します。

1
Coin

次に、右側パネルを Inspector から次へ切り替えます。

1
シグナル

Godot の UI ではこのパネルです。

1
Node

自作シグナルを探します。

1
collected()

これをダブルクリックします。

受信ノードとして次を選びます。

1
Main

Connect を押すと、Godot は Main のスクリプトに次のような関数を生成します。

1
2
func _on_coin_collected() -> void:
    pass

これを次のように変更します。

1
2
3
4
5
6
var score: int = 0


func _on_coin_collected() -> void:
    score += 1
    print("現在のスコア:", score)

実行してコインを取得すると、次のように出力されます。

1
現在のスコア:1

方法 2:コードでシグナルを接続する

エディタを使わず、main.gd で直接接続することもできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
extends Node2D

var score: int = 0

@onready var coin: Area2D = $Coin


func _ready() -> void:
    coin.collected.connect(_on_coin_collected)


func _on_coin_collected() -> void:
    score += 1
    print("現在のスコア:", score)

重要な行はこれです。

1
coin.collected.connect(_on_coin_collected)

意味は:

1
2
3
送信者:coin
シグナル:collected
受信関数:_on_coin_collected

Godot 4 では、Signal オブジェクトから直接 connect() を呼ぶ書き方が推奨されます。

1
coin.collected.connect(_on_coin_collected)

Godot 3 の古いチュートリアルの接続書式をそのまま使わないようにしましょう。

シグナルにデータを持たせる

シグナルは「何かが起きた」と通知するだけでなく、データも渡せます。

たとえばコインごとに点数が違う場合:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
extends Area2D

signal collected(value: int)

@export var value: int = 1


func _ready() -> void:
    body_entered.connect(_on_body_entered)


func _on_body_entered(body: Node2D) -> void:
    if body.name != "Player":
        return

    collected.emit(value)
    queue_free()

通常のコイン:

1
value = 1

大きなコイン:

1
value = 5

Main 側も引数を受け取ります。

1
2
3
4
5
6
7
var score: int = 0


func _on_coin_collected(value: int) -> void:
    score += value
    print("獲得:", value)
    print("合計スコア:", score)

通信の流れ:

1
2
3
4
5
Coin:取得された。価値は 5
Main:value = 5 を受け取る
score += 5

Coin が直接 UI を変更しないほうがよい理由

次のような書き方はおすすめしません。

1
2
3
func _on_body_entered(body: Node2D) -> void:
    get_node("../CanvasLayer/ScoreLabel").text = "1"
    queue_free()

これだと Coin が Main の具体的なノード構造に依存します。

  • Coin は ScoreLabel の場所を知っている必要がある。
  • Coin はスコアの保存方法を知っている必要がある。
  • Coin は UI の更新方法を知っている必要がある。

UI ノードを移動しただけで、Coin のパスが壊れる可能性があります。

より良い構造は次です。

1
2
3
4
5
6
7
8
Coin の責任:
「取得された」と知らせる。

Main の責任:
スコアを増やす。

UI の責任:
スコアを表示する。

つまり:

1
2
3
4
5
6
7
Coin
  │ collected(value)
Main
  │ score を更新
ScoreLabel

これが、シグナルによってノード間の結合を下げられる理由です。

混同しやすい 3 つの概念

signal

シグナルを宣言します。

1
signal collected

通知タイプを作るという意味です。

connect()

シグナルが発せられた後に呼ぶ関数を指定します。

1
collected.connect(_on_collected)

emit()

実際にシグナルを発します。

1
collected.emit()

覚え方:

1
2
3
signal  = 通知を定義する
connect = 受信者を決める
emit    = 通知を発する

おすすめのコインコード

coin.gd

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
extends Area2D

signal collected(value: int)

@export var value: int = 1


func _ready() -> void:
    body_entered.connect(_on_body_entered)


func _on_body_entered(body: Node2D) -> void:
    if body.name != "Player":
        return

    collected.emit(value)
    queue_free()

main.gd

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
extends Node2D

var score: int = 0

@onready var score_label: Label = $CanvasLayer/ScoreLabel
@onready var coin: Area2D = $Coin


func _ready() -> void:
    coin.collected.connect(_on_coin_collected)
    update_score_label()


func _on_coin_collected(value: int) -> void:
    score += value
    update_score_label()


func update_score_label() -> void:
    score_label.text = "スコア:%d" % score

大事なのは次の一点です。

シグナルは何が起きたかを通知するもので、他のすべてのノードが次に何をするかを決めるものではありません。

これを理解すると、Godot のボタン、コイン、敵、タイマー、体力変更、UI 更新をより整理しやすくなります。

记录并分享
Hugo で構築されています。
テーマ StackJimmy によって設計されています。