Godot の「シグナル」は、ノードが発する通知だと考えると分かりやすいです。
何かが起きた。関心のある相手がそれを処理する。
例:
- ボタン:押された。
- コイン:プレイヤーに取得された。
- プレイヤー:体力が変わった。
- 敵:死亡した。
- タイマー:時間になった。
他のノードはこれらのシグナルに接続し、シグナルが発せられたときに関数を実行できます。送信側は受信側を直接探す必要がなく、ノード同士の依存を減らせます。
シグナルの 3 つの基本ステップ
シグナルの流れは通常 3 つの手順です。
- シグナルを宣言する。
- シグナルを接続する。
- シグナルを発する。
ドアベルにたとえると:
| 概念 | たとえ |
|---|---|
signal |
ドアベルを設置する |
connect |
ドアベルを室内チャイムにつなぐ |
emit |
ドアベルを押す |
| 受信関数 | 中の人が聞いてドアを開ける |
覚え方は次です。
|
|
Godot のシグナルは 2 種類
Godot のシグナルは、大きく内蔵シグナルと自作シグナルに分けられます。
内蔵シグナル
内蔵シグナルは、Godot のノードが最初から持っているシグナルです。
よくある例:
Button.pressedTimer.timeoutArea2D.body_enteredArea2D.body_exited
コインノードが Area2D を継承しているなら、最初から body_entered シグナルを持っています。CharacterBody2D がコインの当たり判定領域に入ると、Godot が自動的にこのシグナルを発します。
自作シグナル
自作シグナルは自分で宣言します。
たとえばコインが取得されたことを表すなら:
|
|
この collected は Godot の内蔵シグナルではなく、自分で定義した通知タイプです。
コイン例の 2 層のシグナル
簡単なコインスクリプトは次のように書けます。
|
|
ここでは実際に 2 層のシグナルを使っています。
第 1 層:body_entered
このコード:
|
|
意味は次です。
物理オブジェクトが Coin の
Area2Dに入ったら、_on_body_entered()を呼ぶ。
流れ:
|
|
渡される body は、コイン領域に入ったノードです。Player が入ったなら:
|
|
第 2 層:collected
このコード:
|
|
はシグナル宣言です。Coin が collected という通知を発せるようになります。
次に:
|
|
これは次の通知を実際に発するという意味です。
このコインは取得された。
ただし、シグナルを発したからといって、必ず誰かが受け取るとは限りません。
どのノードも collected に接続していなければ、シグナルは発せられますが追加の処理は起きません。コインが消えるのは、次の行があるからです。
|
|
collected.emit() が自動的にコインを消しているわけではありません。
完全な通信の流れ
プレイヤーがコインに触れた後の流れは次です。
|
|
ここには 2 つの送信者があります。
|
|
body_entered は Coin に「何かが領域に入った」と伝えます。
collected は Main に「このコインが取得された」と伝えます。
Main にコインのシグナルを受け取らせる
シーン構造が次だとします。
|
|
シグナルはエディタでもコードでも接続できます。
方法 1:Godot エディタで接続する
main.tscn を開きます。
まず、シーンツリーで次を選択します。
|
|
次に、右側パネルを Inspector から次へ切り替えます。
|
|
Godot の UI ではこのパネルです。
|
|
自作シグナルを探します。
|
|
これをダブルクリックします。
受信ノードとして次を選びます。
|
|
Connect を押すと、Godot は Main のスクリプトに次のような関数を生成します。
|
|
これを次のように変更します。
|
|
実行してコインを取得すると、次のように出力されます。
|
|
方法 2:コードでシグナルを接続する
エディタを使わず、main.gd で直接接続することもできます。
|
|
重要な行はこれです。
|
|
意味は:
|
|
Godot 4 では、Signal オブジェクトから直接 connect() を呼ぶ書き方が推奨されます。
|
|
Godot 3 の古いチュートリアルの接続書式をそのまま使わないようにしましょう。
シグナルにデータを持たせる
シグナルは「何かが起きた」と通知するだけでなく、データも渡せます。
たとえばコインごとに点数が違う場合:
|
|
通常のコイン:
|
|
大きなコイン:
|
|
Main 側も引数を受け取ります。
|
|
通信の流れ:
|
|
Coin が直接 UI を変更しないほうがよい理由
次のような書き方はおすすめしません。
|
|
これだと Coin が Main の具体的なノード構造に依存します。
- Coin は
ScoreLabelの場所を知っている必要がある。 - Coin はスコアの保存方法を知っている必要がある。
- Coin は UI の更新方法を知っている必要がある。
UI ノードを移動しただけで、Coin のパスが壊れる可能性があります。
より良い構造は次です。
|
|
つまり:
|
|
これが、シグナルによってノード間の結合を下げられる理由です。
混同しやすい 3 つの概念
signal
シグナルを宣言します。
|
|
通知タイプを作るという意味です。
connect()
シグナルが発せられた後に呼ぶ関数を指定します。
|
|
emit()
実際にシグナルを発します。
|
|
覚え方:
|
|
おすすめのコインコード
coin.gd:
|
|
main.gd:
|
|
大事なのは次の一点です。
シグナルは何が起きたかを通知するもので、他のすべてのノードが次に何をするかを決めるものではありません。
これを理解すると、Godot のボタン、コイン、敵、タイマー、体力変更、UI 更新をより整理しやすくなります。