シグナルの使用¶
このレッスンでは、シグナルを見ていきます。これらは、ボタンが押された、など特定のことが起こったときにノードが発信するメッセージです。他のノードはシグナルに接続し、イベントが発生したときに関数を呼び出すことができます。
シグナルはGodotに組み込まれた委任メカニズムで、あるゲームオブジェクトが別のゲームオブジェクトの変更に反応する際に、それらを相互参照させることなく反応できるようにするものです。シグナルを使うと、 結合度 を制限し、コードの柔軟性を保つことができます。
例えば、画面上にプレイヤーの体力を表すライフバーがあるとします。プレイヤーがダメージを受けたり、回復薬を使ったりした場合には、その変化をバーに反映されるようにしたいとします。これを実現するために、Godotではシグナルを使用します。
注釈
概要で述べたように、シグナルはGodotバージョンのobserverパターンです。詳しくは 次を参照ください: https://gameprogrammingpatterns.com/observer.html
今度はシグナルを使って、前パート ( プレイヤーの入力を聞く ) で作ったGodotのアイコンがボタンを押すことで動いたり止まったりするようにします。
シーンの設定¶
ゲームにボタンを追加するために、 前回のレッスンで作成した Sprite.tscn
スクリプトとボタン両方を持つための main
シーンを新規に作成します。
メニューの「シーン -> 新規シーン」で、新しいシーンを作成します。
シーンドック内の2Dシーンボタンをクリックすることで、2Dのルートノードが作成されます。
作成されたNode2Dの上に、ファイルシステム内の Sprite.tscn
ファイルをドラッグアンドドロップします。
もう一つ別のノードをSpriteと同列に追加します。Node2Dを右クリックし、子ノードを追加を選択します。
Buttonノードを検索し、作成をクリックします。
デフォルトではノードは小さく作成されます。ハンドルの右下をクリック&ドラッグし、サイズを変更します。
ハンドルが表示されない場合は、ツールバー上で選択ツールが選択されていることを確認してください。
ボタンをクリック&ドラッグし、Spriteの近くへ動かします。
インスペクター上のTextプロパティでButtonにラベル名をつけることができます。"Toggle motion"と入力しましょう。
シーンツリーとビューポートは次のようになります。
作成したシーンを保存しましょう。 :kbd:`F6`で実行できます。この時点では、ボタンは表示されますが、押しても何も起こりません。
エディタ内でシグナルを接続する¶
ここでは、Button の "pressed" シグナルを Sprite に接続し、その動作のオン・オフを切り替える新しい関数を呼び出したいと思います。前のレッスンで行ったように、Spriteノードにスクリプトをアタッチする必要があります。
ノードドック内でシグナルを接続することができます。Buttonノードを選択し、インスペクターの横にあるノードをクリックします。
選択したノードで利用可能なシグナルが表示されます。
"pressed"シグナルをダブルクリックし、ノード接続ウィンドウを開きます。
Spriteノードにシグナルを接続できます。ノードはreceiver関数を必要とします。これは、ボタンがシグナルを発したときにGodotが呼び出す関数です。エディタにより一つ自動で作成されます。慣習として、これらのコールバック関数は"_on_ノード名_シグナル名"で命名されます。この場合、"_on_Button_pressed"となります。
注釈
ノードドックよりシグナルを接続する場合、簡易モードと高度な設定を利用できます。簡易モードではスクリプトが付随しているノードにのみ接続でき、そのノードに新しいコールバック関数を作成します。
高度な設定("Advanced")では、任意のノードと任意のビルトイン関数に接続し、コールバックに引数を追加し、オプションを設定することができます。ウィンドウの右下にある"Advanced"ボタンをクリックすることで、モードを切り替えることができます。
接続ボタン("Connect")をクリックすると、シグナルの接続が完了し、スクリプトのワークスペースにジャンプします。左側のマージンに接続アイコンのある新しいメソッドが表示されるはずです。
このアイコンをクリックすると、ウィンドウがポップアップし、接続に関する情報が表示されます。この機能は、エディターでノードを接続する場合のみ有効です。
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 を検索して、対応するノードを追加します。シーンは次のようになります。
Timerノードを選択した状態で、インスペクタに移動し、Autostartプロパティをチェックします。
スプライトの横にあるスクリプトアイコンをクリックして、スクリプトのワークスペースに戻ります。
ノードをコードで接続するには、2つの操作が必要です。
Sprite から Timer への参照を取得します。
Timer の
connect()
メソッドを呼び出します。
注釈
コードでシグナルに接続するには、シグナルを受信したいノードのconnect()
メソッドを呼び出す必要があります。この場合、Timer のタイムアウト("timeout")シグナルを受信します。
シーンがインスタンス化されたときにシグナルを接続したいのですが、それには Node._ready() 組み込み関数を使用して接続できます。この関数はノードが完全にインスタンス化されるとエンジンから自動的に呼び出されます 。
現在のノードに関連するノードの参照を取得するには、 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
の行で値を反転します。visible
が true
なら、false
に、また逆の場合も同様です。
このシーンを今実行すると、スプライトが1秒間隔で点滅するのがわかるでしょう。
完全なスクリプト¶
これで、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")タブに表示され、他のシグナルと同様に接続することができます。
コードを介してシグナルを発信するには、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ゲームを作成し、これまでに学んだことを実践します。