シグナルの使用

このレッスンでは、シグナルを見ていきます。これらは、ボタンが押された、など特定のことが起こったときにノードが発信するメッセージです。他のノードはシグナルに接続し、イベントが発生したときに関数を呼び出すことができます。

これはGodotに組み込まれた委任メカニズムで、あるゲームオブジェクトが別のゲームオブジェクトの変更に反応する際に、それらを相互参照させることなく反応できるようにするものです。シグナルを使うと、結合度 を制限し、コードの柔軟性を保つことができます。

例えば、画面上にプレイヤーの体力を表すライフバーがあるとします。プレイヤーがダメージを受けたり、回復薬を使ったりした場合には、その変化をバーに反映されるようにしたいとします。これを実現するために、Godotではシグナルを使用します。

注釈

概要で述べたように、シグナルはGodotバージョンのobserverパターンです。詳しくは 次を参照ください: https://gameprogrammingpatterns.com/observer.html

今度はシグナルを使って、前パート ( プレイヤーの入力を聞く ) で作ったGodotのアイコンがボタンを押すことで動いたり止まったりするようにします。

シーンの設定

ゲームにボタンを追加するために、 前回のレッスンで作成した Sprite.tscn スクリプトとボタン両方を持つための main シーンを新規に作成します。

メニューの「シーン -> 新規シーン」で、新しいシーンを作成します。

../../_images/signals_01_new_scene.png

シーンドック内の2Dシーンボタンをクリックすることで、2Dのルートノードが作成されます。

../../_images/signals_02_2d_scene.png

作成されたNode2Dの上に、ファイルシステム内の Sprite.tscn ファイルをドラッグアンドドロップします。

../../_images/signals_03_dragging_scene.png

もう一つ別のノードをSpriteと同列に追加します。Node2Dを右クリックし、子ノードを追加を選択します。

../../_images/signals_04_add_child_node.png

Buttonノードを検索し、作成をクリックします。

../../_images/signals_05_add_button.png

デフォルトではノードは小さく作成されます。ハンドルの右下をクリック&ドラッグし、サイズを変更します。

../../_images/signals_06_drag_button.png

ハンドルが表示されない場合は、ツールバー上で選択ツールが選択されていることを確認してください。

../../_images/signals_07_select_tool.png

ボタンをクリック&ドラッグし、図の近くへ動かします。

インスペクター上のTextプロパティでラベル名をつけることができます。

../../_images/signals_08_toggle_motion_text.png

シーンツリーとビューポートは次のようになります。

../../_images/signals_09_scene_setup.png

作成したシーンを保存します。 :kbd:`F6`で実行します。

エディタ内でシグナルを接続する

ここでは、Button の "pressed" シグナルを Sprite に接続し、その動作のオン・オフを切り替える新しい関数を呼び出したいと思います。前のレッスンで行ったように、Spriteノードにスクリプトをアタッチする必要があります。

ノードドック内でシグナルを接続することができます。Buttonノードを選択し、インスペクターの横にあるノードをクリックします。

../../_images/signals_10_node_dock.png

選択したノードで利用可能なシグナルが表示されます。

../../_images/signals_11_pressed_signals.png

"pressed"シグナルをダブルクリックし、ノード接続ウィンドウを開きます。

../../_images/signals_12_node_connection.png

Spriteノードにシグナルを接続できます。ノードはreceiver関数を必要とします。これは、ボタンがシグナルを発したときにGodotが呼び出す関数です。エディタにより一つ自動で作成されます。慣習として、これらのコールバック関数は"_on_ノード名_シグナル名"で命名されます。この場合、"_on_Button_pressed"となります。

注釈

ノードドックよりシグナルを接続する場合、簡易モードと高度な設定を利用できます。簡易モードではスクリプトが付随しているノードにのみ接続でき、そのノードに新しいコールバック関数を作成します。

../../_images/signals_advanced_connection_window.png

高度な設定("Advanced")では、任意のノードと任意のビルトイン関数に接続し、コールバックに引数を追加し、オプションを設定することができます。ウィンドウの右下にあるラジオボタンをクリックすることで、モードを切り替えることができます。

接続ボタン("Connect")をクリックすると、シグナルの接続が完了し、スクリプトのワークスペースにジャンプします。左側のマージンに接続アイコンのある新しいメソッドが表示されるはずです。

../../_images/signals_13_signals_connection_icon.png

このアイコンをクリックすると、ウィンドウがポップアップし、接続に関する情報が表示されます。この機能は、エディターでノードを接続する場合のみ有効です。

../../_images/signals_14_signals_connection_info.png

passキーワードの行を、ノードの動きを切り替えるコードに置き換えましょう。

スプライトが動くのは、_process()関数内のコードのおかげです。Godot は処理のオンとオフを切り替えるメソッドを提供しています : Node.set_process()。Nodeクラスの別のメソッドであるis_processing()は、アイドル処理が有効であれば trueを返します。notキーワードを使って、値を反転させることができます。

func _on_Button_pressed():
    set_process(not is_processing())

この関数はボタンを押したときに、処理を切り替え、アイコンの動作のON/OFFを切り替えます。

ゲームを試す前に、 _process()関数を単純化して、ユーザー入力を待たずにノードを自動的に移動させる必要があります。次のコードに置き換えます (これは 2 つ前のレッスンで見ています)。

func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta

完成した Sprite.gdコードは次のようになります。

extends Sprite

var speed = 400
var angular_speed = PI


func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta


func _on_Button_pressed():
    set_process(not is_processing())

すぐにシーンを実行し、ボタンをクリックして、スプライトの開始と停止を確認します。

コード経由でシグナルを接続する

エディタを使用する代わりに、コードを介してシグナルを接続することができます。これは、スクリプトの中でノードを作成したり、シーンをインスタンス化するときに必要です。

ここで、別のノードを使ってみましよう。Godot には Timer ノードがあり、スキルのクールダウン時間や武器のリロードなどを実装するのに便利です。

2Dワークスペースに戻りましょう。ウィンドウ上部の "2D" のテキストをクリックするか、 Ctrl + F1 (macOS では Alt + 1) を押します。

シーン("Scene") ドックで、Sprite ノードを右クリックし、新しい子ノードを追加します。Timer を検索して、対応するノードを追加します。シーンは次のようになります。

../../_images/signals_15_scene_tree.png

Timerノードを選択した状態で、インスペクタに移動し、Autostartプロパティをチェックします。

../../_images/signals_18_timer_autostart.png

スプライトの横にあるスクリプトアイコンをクリックして、スクリプトのワークスペースに戻ります。

../../_images/signals_16_click_script.png

ノードをコードで接続するには、2つの操作が必要です。

  1. Sprite から Timer への参照を取得します。

  2. Timer のconnect()メソッドを呼び出します。

注釈

コードでシグナルに接続するには、シグナルを受信したいノードのconnect()メソッドを呼び出す必要があります。この場合、Timer のタイムアウト("timeout")シグナルを受信します。

We want to connect the signal when the scene is instantiated, and we can do that using the Node._ready() built-in function, which is called automatically by the engine when a node is fully instantiated.

現在のノードに関連するノードの参照を取得するには、 Node.get_node() というメソッドを使用します。この参照は変数に格納することができます。

func _ready():
    var timer = get_node("Timer")

関数 get_node()は Sprite の子を調べ、その名前によってノードを取得します。たとえば、エディタで Timer ノードの名前を "BlinkingTimer" に変更した場合、呼び出しをget_node("BlinkingTimer")に変更する必要があります。

これで、_ready()関数の中で Timer を Sprite に接続することができます。

func _ready():
    var timer = get_node("Timer")
    timer.connect("timeout", self, "_on_Timer_timeout")

この行は次のようになります。Timer の "timeout" シグナルを、スクリプトが接続されているノード(self)に接続します。タイマーが "timeout" を発行したら、関数 "_on_Timer_timeout" を呼び出します。スクリプトの下部にこの関数を追加し、それを使用してスプライトの表示を切り替えましょう。

func _on_Timer_timeout():
    visible = not visible

visibleのプロパティはブール値で、ノードの可視性を制御します。visible = not visibleの行で値を反転します。visibletrueなら、falseに、また逆の場合も同様です。

完全なスクリプト

これで、Godot アイコンが動いて点滅する、小さなデモを終了します! 参考までに、以下がSprite.gdの完全なファイルです。

extends Sprite

var speed = 400
var angular_speed = PI


func _ready():
    var timer = get_node("Timer")
    timer.connect("timeout", self, "_on_Timer_timeout")


func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta


func _on_Button_pressed():
    set_process(not is_processing())


func _on_Timer_timeout():
    visible = not visible

カスタムシグナル

注釈

このセクションは、独自のシグナルを定義して使用する方法についての参考であり、前のレッスンで作成したプロジェクトを使用するものではありません。

スクリプトでカスタムシグナルを定義することができます。例えば、プレイヤーの体力が0になったときにゲームオーバー画面を表示させたいとします。そのためには、体力が0になったときに、"died" (死亡)や "health_depleted"(体力の枯渇) という名前のシグナルを定義することができます。

extends Node2D

signal health_depleted

var health = 10

注釈

シグナルは発生したばかりのイベントを表現するため、通常、動作を表す過去形の動詞をシグナルの名前に使用します。

作成したシグナルは、組み込まれたシグナルと同じように動作します。シグナルはノード("Node")タブに表示され、他のシグナルと同様に接続することができます。

../../_images/signals_17_custom_signal.png

コードを介してシグナルを発信するには、emit_signal() 関数を使用します。

func take_damage(amount):
    health -= amount
    if health <= 0:
        emit_signal("health_depleted")

シグナルは、オプションで1つ以上の引数を宣言できます。カッコの中に引数の名前を指定します:

extends Node

signal health_changed(old_value, new_value)

注釈

シグナル引数はエディタのノードドックに表示され、Godotはそれらを使用してコールバック関数を生成できます。しかしながら、シグナルを発信するときに、任意の数の引数を発行できます。正しい値を出力するのはあなた次第です。

シグナルと一緒に値を出力するには、emit_signal()関数に追加の引数として値を追加します。

func take_damage(amount):
    var old_health = health
    health -= amount
    emit_signal("health_changed", old_health, health)

概要

Godot ではどのノードも、ボタンが押されるなど、何か特定のことが起こるとシグナルを発し ます。他のノードは個々のシグナルに接続し、選択されたイベントに反応することができます。

シグナルには多くの用途があります。これらを使用すると、ゲームワールドに出入りするノード、衝突、領域に出入りするキャラクター、サイズが変化するインターフェイスの要素など、多くのことに反応できます。

たとえば、コインの見た目をした Area2D は、プレイヤーの物理ボディが衝突形状(コリジョンシェイプ)に入るたびに body_enteredシグナルを発し、プレイヤーがそれを収集したタイミングを知ることができます。

次のセクション、 最初の2Dゲーム では、完全な2Dゲームを作成し、これまでに学んだことを実践します。