コードを使用してゲームのUIを制御する¶
イントロ¶
このチュートリアルでは、キャラクターをライフバーに接続し、その健康状態の表示をライブバーで行います。

ここで作成するのは、キャラクターがヒットを受けたときにバーとカウンターをアニメーションさせるものです。キャラクターが死ぬと徐々に消えます。¶
次の方法について学習します:
シグナルを使ってキャラクターをGUIに 接続 する方法
GDscriptを使用してGUIを 制御する 方法
Tweenノードを使ってライフ バーをアニメーション化する方法
代わりにインターフェイスを設定する方法を学びたい場合は、ステップバイステップのUIチュートリアルを参照してください:
ゲームをコーディングするときには、最初にゲームプレイのコアとなる部分、つまり動作のメカニズム、プレイヤーの入力、勝敗条件を構築しなければなりません。その後にUIを設計します。可能であれば、プロジェクトを構成するすべての要素を別々にしておきます。各キャラクタは独自のシーンの中に、独自のスクリプトを持って存在する必要があり、UI要素もそれと同様です。これによってバグを防ぎ、プロジェクトを管理しやすくし、異なるチームメンバーがゲームのそれぞれの部分で作業できるようになります。
中核となるゲームプレイとUIの準備ができたら、何らかの方法でそれらを接続する必要があります。この例では、一定の時間間隔でプレイヤーを攻撃する敵がいます。プレイヤーがダメージを受けたときは、ライフバーには更新してほしいでしょう。
これを行うには、 シグナル を使用します。
注釈
シグナルは、ObserverパターンのGodot版です。メッセージを送信することができます。他のノードは、シグナルを 発信 するオブジェクトに接続して、情報を受信できます。これは強力なツールで、ユーザーインターフェイスや実績システムで多用されています。ただし、全ての場面で使用する必要はありません。 2つのノードを接続すると、ノード間の結合度が増します。接続が多いと、それだけ管理するのが難しくなります。詳細については、GDquestの signals video tutorial(英語)をご覧ください。
スタートプロジェクトをダウンロードして参照する¶
Godotプロジェクトをダウンロード(ui_code_life_bar.zip
)してください。チュートリアルに必要なすべての素材やスクリプトが入っています。zipアーカイブを解凍して、2つのフォルダ start と end を取得します。
Godotに start
プロジェクトを読み込みます。 ファイルシステム
ドックで、LevelMockup.tscnをダブルクリックして開きます。 2人のキャラクターが向かい合っているRPGゲームのモックアップです。ピンク色の敵は、死ぬまで一定の時間間隔で緑色の正方形を攻撃し、ダメージを与えます。ゲームを試してみてください: 基本的な戦闘の仕組みはすでに機能しています。ですが、キャラクターはライフバーに接続されていないため、GUI
は何もしません。
注釈
これは、ゲームのコーディング方法の典型です。まず最初にゲームプレイのコアを実装し、プレイヤー死亡の処理ができてから、ようやくインターフェイスを追加します。なぜなら、UIはゲーム内で起こっていることに耳を傾けているからです。なので、他のシステムがまだ存在していない段階では動きません。ゲームプレイのプロトタイプを作成してテストする前にUIを設計しても、多分うまく機能せずに最初からつくり直すはめになるでしょう。
シーンには、背景スプライト、GUI、および2つのキャラクターが含まれています。

シーンツリー (GUIシーンの子を表示するように設定)¶
GUIシーンは、ゲームのすべてのグラフィカル ユーザーインターフェイス(GUI)をカプセル化します。シーン内に存在するノードへのパスを取得するためのベアボーン スクリプトが付属しています:
onready var number_label = $Bars/LifeBar/Count/Background/Number
onready var bar = $Bars/LifeBar/TextureProgress
onready var tween = $Tween
public class Gui : MarginContainer
{
private Tween _tween;
private Label _numberLabel;
private TextureProgress _bar;
public override void _Ready()
{
// C# doesn't have an onready feature, this works just the same.
_bar = (TextureProgress) GetNode("Bars/LifeBar/TextureProgress");
_tween = (Tween) GetNode("Tween");
_numberLabel = (Label) GetNode("Bars/LifeBar/Count/Background/Number");
}
}
number_label
は、残り体力を数値として表示します。 これはLabel
ノードですbar
はライフバーそのものです。 これはTextureProgress
ノードですtween
はコンポーネント スタイルなノードで、他のノードにある任意の値またはメソッドをアニメーション化および制御できます。
注釈
このプロジェクトでは、ゲームジャムや小さなゲームに適したシンプルな構造を使用しています。
プロジェクトのルートの res :// フォルダに LevelMockup があります。これがメインのゲームシーンであり、ここで作業します。ゲームを構成するすべてのコンポーネントは、 scenes/ フォルダにあります。 assets/ フォルダには、ゲームのスプライトとHPカウンタのフォントが含まれています。 scripts/ フォルダには、敵のスクリプト、プレイヤーのスクリプト、GUIコントローラのスクリプトがあります。
シーンツリーのノードの右側にあるシーン編集アイコンをクリックして、エディタでシーンを開きます。 「LifeBar」と「EnergyBar」はそれ自体サブシーンです。

シーンツリー (Playerシーンの子を表示するように設定)¶
プレイヤーのmax_healthを使用してライフバーをセットアップする¶
プレイヤーの現在の状態を何らかの方法でGUIに伝え、ライフバーのテクスチャを更新し、残りの体力を画面左上のHPカウンターに表示する必要があります。そのために、プレイヤーがダメージを受けるたびに、プレイヤーの体力をGUIに送信します。GUIは、この値で Lifebar
ノードと Number
ノードを更新します。
ここで数字を表示させることもできますが、その前に、正しい比率で更新するため、バーの max_value を初期化する必要があります。なので最初のステップは、緑のキャラクターの max_health
を GUI
に伝えることです。
ちなみに
TextureProgress であるバーは、デフォルトで max_value が 100 になっています。 キャラクターの体力を数字で表示する必要がない場合は、その max_value プロパティを変更する必要はありません。代わりに、 Player から GUI にパーセンテージを送信します: health / max_health * 100。

シーンドック内の GUI
の右側にあるスクリプトアイコンをクリックして、スクリプトを開きます。 _ready
関数にて、プレイヤーの max_health
を新しい変数に格納し、それを使って bar
の max_value
を設定します:
func _ready():
var player_max_health = $"../Characters/Player".max_health
bar.max_value = player_max_health
public override void _Ready()
{
// Add this below _bar, _tween, and _numberLabel.
var player = (Player) GetNode("../Characters/Player");
_bar.MaxValue = player.MaxHealth;
}
1つずつ見てみましょう。 $"../Characters/Player"
は簡略記法で、シーンツリーで1つ上のノードに移動し、そこから Characters/Player
ノードを取得します。これにより目的のノードにアクセスできます。文の2番目の部分、 .max_health
は、Playerノード上の max_health
にアクセスします。
2行目は、この値を bar.max_value
に割り当てます。 この2行を1つにまとめることもできますが、チュートリアルの後半でもう一度 player_max_health
を使う必要があります。
Player.gd
はゲーム開始時に health
を max_health
に設定するので、このまま作業できます。なぜ max_health
を使用しているのでしょう? これには 2つの理由があります:
health
が常に max_health
と等しい保証はありません。このゲームの将来バージョンでは、プレイヤーが体力をいくらか失った状態でも、次のレベルを読み込めるようにするかもしれません。
注釈
ゲーム内でシーンを開くと、Godotはシーンドック内の順序に従って、上から下にノードを1つずつ作成していきます。 GUI と Player は、同じノードブランチの一部ではありません。これらが相互にアクセスするときに両方とも存在することを確認するには、 _ready 関数を使用する必要があります。Godotは、すべてのノードをロードした直後、ゲームを開始する前に _ready を呼び出します。これは、すべてを設定してゲームセッションを準備するには最適な関数です。_ready の詳細についてはスクリプト(続き)を参照して下さい。
プレイヤーがヒットしたときにシグナルで体力を更新する¶
GUIは Player
から health
の更新値を受け取る準備ができています。これを実現するために、シグナルを使用します。
注釈
たくさんの便利な組み込みシグナルがあり、たとえば全てのノードが持つenter_tree と exit_tree は、それぞれ作成されたときと破棄されたときに発信します。signal キーワードを使用すれば、独自のシグナルを作成することもできます。 Player ノードには、そうして用意された2つのシグナル died と health_changed がすでにあります。
_process
関数内で Player
ノードを直接取得し、体力値を確認するのはどうでしょう? この方法でノードにアクセスすると、ノード間に緊密な結合ができてしまいます。これは控えめにやれば、うまくいくかもしれません。しかし、ゲームが大きくなるにつれて、より多くの接続をすることになるはずです。この方法でノードを取得して行くと、すぐに複雑になってしまいます。問題はそれだけではありません: _process
関数内で、常に他のノードの状態変化を確認する必要があります。このチェックは(60FPSのゲームなら)1秒間に60回行われ、コードには実行順序があるので恐らくゲームが中断されることになるでしょう。
特定のフレームで、更新「前」に別のノードのプロパティを参照すると、その直前のフレームから値が取得されます。これは、修正が難しい不明瞭なバグにつながります。一方、シグナルは変化が起こった直後に発せられます。新しい情報を 確実 に入手できるのです。また、変更が行われた「直後」に、接続されたノードの状態を更新します。
注釈
Observerパターンは、シグナルの基盤ですが、ノードのブランチ間に若干の結合を追加します。しかし大抵の場合、2つの異なるクラス間での通信においては、ノードに直接アクセスするよりも軽量で安全です。親ノードから子の値を取得することは問題ありません。しかし、2つの別々のブランチをまたいで作業しているなら、シグナルを優先したほうがいいでしょう。Observerパターンの詳細については、Game Programming Patternsの Observer pattern (英語)を参照してください。完全版の本はオンラインで無料で入手できます。
これを念頭に置いて、 GUI
を Player
に接続しましょう。まずシーンドックの Player
ノードをクリックして選択します。インスペクタに移動し、「ノード」タブをクリックします。ここは、選択したノードからのシグナルを受信待機させるために、ノードを接続する場所です。
最初のセクションでは、 Player.gd
で定義されているカスタムシグナルを一覧表示します:
キャラクターが死んだときには
died
が出力されます。 このシグナルは後で、UIを非表示にするために使用します。health_changed
は、キャラクタがヒットを受けたときに放出されます。

これから「health_changed」シグナルに接続します¶
health_changed
を選択し、右下隅の「接続」ボタンをクリックして、「シグナルの接続」ウィンドウを開きます。左側で、この信号を受信待機するノードを選択できます。 GUI
ノードを選択します。画面の右側にて、オプションの値を信号に加えることができます。これについては、すでに Player.gd
内で済ませてあります。コード内にて行うよりも不便なので、このウィンドウを使うなら、通常は引数を追加しすぎないようにすることをお勧めします。

GUIノードが選択された「シグナル接続」ウィンドウ¶
ちなみに
必要に応じて、コード内でノードを接続できます。ただし、エディタから実行することには次の利点2つがあります:
Godotはあなたに代わって、接続されたスクリプト内に新しいコールバック関数を書けます
シーンドック内で、信号を発信するノードの横に発信元アイコンが表示されます
ウィンドウの下部に、選択したノードへのパスが表示されます。"Method in Node"と呼ばれる 2 行目に注目して下さい。これは、シグナルが出力されるときに呼び出される GUI
ノード上のメソッドです。このメソッドは、シグナルと共に送信された値を受け取り、それらを処理できるようにします。右側を見ると、デフォルトで「関数を作成」ラジオボタンがオンになっています。ウィンドウの下部にある接続ボタンをクリックします。Godot は GUI
ノード内にメソッドを作成します。スクリプトエディタが開き、新しい _on_Player_health_changed
関数内にカーソルが置かれます。
注釈
エディタからノードを接続すると、Godotは _on_EmitterName_signal_name (_on_(発信元名)_(シグナル名))
のパターンでメソッド名を生成します。すでにそのメソッドが書かれている場合は、「関数を作成」オプションが有効であっても内容は保持されます。この名前は任意の名前に置き換えることができます。

Godotはコールバック メソッドを作成し、あなたに見せます¶
関数名の後の括弧内に player_health
引数を追加してください。プレイヤーが health_changed
シグナルを発信すると、現在の health
も一緒に送信します。コードは次のようになります。
func _on_Player_health_changed(player_health):
pass
public void OnPlayerHealthChanged(int playerHealth)
{
}
注釈
エンジンは PascalCase を snake_case に変換しません。C#の例では、メソッド名に PascalCase を、メソッドパラメータに camelCase を使用しています。これは公式のC#命名規則に従うものです。

Player.gdでは、Playerがhealth_changedシグナルを発信すると、その体力値も送信します¶
_on_Player_health_changed
内で、 update_health
と呼ばれる2番目の関数を呼び出して、 player_health
変数を渡します。
注釈
`LifeBar`と`Number`の体力値を直接更新できます。 代わりにこの方法を使用する理由は2つあります:
この名前は、プレイヤーがダメージを受けたときに GUI の体力(health)カウントを更新することを、将来の自分自身やチームメイトのために明確にします
この方法は少し後で再利用します
_on_Player_health_changed
の下に新しい update_health
メソッドを作成します。唯一の引数としてnew_valueを取ります:
func update_health(new_value):
pass
public void UpdateHealth(int health)
{
}
このメソッドは以下を行う必要があります:
Number
ノードのtext
を文字列に変換されたnew_value
に設定しますTextureProgress
のvalue
をnew_value
に設定します
func update_health(new_value):
number_label.text = str(new_value)
bar.value = new_value
public void UpdateHealth(int health)
{
_numberLabel.Text = health.ToString();
_bar.Value = health;
}
ちなみに
str
は、ほぼすべての値をテキストに変換する組み込み関数です。 Number
の text
プロパティには文字列が必要なので、それを new_value
に直接割り当てることはできません
また、_ready
関数の最後で update_health
を呼び出して、ゲームの開始時に適切な値で Number
ノードの text
を初期化します。F5 を押してゲームをテストします。攻撃を受けるたびにライフバーが更新されます!

Playerがヒットすると、NumberノードとTextureProgressの両方が更新されます¶
Tweenノードを使用した、自機損失のアニメーション¶
このインターフェイスは機能していますが、いくつかのアニメーションを使用することができます。これは、プロパティをアニメーション化するために不可欠なツールである Tween
ノードを導入する良い機会です。 Tween
は、一定の期間にわたって開始状態から終了状態まで、必要なものをアニメーション化します。たとえば、キャラクタがダメージを受けたときに、 TextureProgress
の体力(health)を現在のレベルから Player
の新しい health
にアニメートできます。
GUI
シーンには、 tween
変数に格納されている Tween
子ノードが既に含まれています。それでは、それを使用してみましょう。 update_health
にいくつかの変更を加える必要があります。
Tween
ノードの interpolate_property
メソッドを使用します。引数は7つあります:
アニメーションするプロパティを所有するノードへの参照
文字列としてのプロパティの識別子
開始値
終了値
アニメーションの継続時間(秒)
トランジションのタイプ
方程式と組み合わせて使用するイージング。
最後の 2 つの引数を組み合わせると、イージング方程式に対応します。これにより、始点から終点まで間に値がどのように変化するかを制御します。
GUI
ノードの横にあるスクリプト・アイコンをクリックして再度開きます。 Number
ノード自体を更新するにはテキストが必要で、 Bar
には浮動小数点または整数が必要です。 interpolate_property
を使用して数値をアニメーションすることはできますが、テキストを直接アニメーションさせることはできません。これを使用して、 animated_health
という名前の新しい GUI変数
をアニメーション化します。
スクリプトの先頭で、新しい変数を定義し、 animated_health
という名前を付け、値を0に設定します。 update_health
メソッドに戻り、その内容をクリアします。 animated_health
値をアニメートします。 Tween
ノードの interpolate_property
メソッドを呼び出します:
func update_health(new_value):
tween.interpolate_property(self, "animated_health", animated_health, new_value, 0.6)
// Add this to the top of your class.
private float _animatedHealth = 0;
public void UpdateHealth(int health)
{
_tween.InterpolateProperty(this, "_animatedHealth", _animatedHealth, health, 0.6f, Tween.TransitionType.Linear,
Tween.EaseType.In);
}
呼出を中断しましょう:
tween.interpolate_property(self, "animated_health", ...
animated_health
を self
、つまり GUI
ノードをターゲットにします。 Tween
の interpolation_property
は、プロパティの名前を文字列として取得します。 ですので "animated_health"
とします。
... _health", animated_health, new_value, 0.6 ...
開始点は、バーの現在の値です。この部分はまだコーディングする必要がありますが、 animated_health
になります。アニメーションの終了点は、 health_changed
後の Player
の health
、つまり new_value
です。 0.6
はアニメーションの秒単位の長さです。
tween.start()
で Tween
ノードをアクティブにするまでアニメーションは再生されません。ノードがアクティブでない場合は、これを一度だけ実行する必要があります。 最後の行の後にこのコードを追加します:
if not tween.is_active():
tween.start()
if (!_tween.IsActive())
{
_tween.Start();
}
注釈
Player の health プロパティをアニメートすることはできますが、そうすべきではありません。キャラクターは命中するとすぐにライフを失います。これにより、いつ死亡したかを知りたいなど、状態を管理しやすくなります。アニメーションは常に別のデータ コンテナーまたはノードに格納する必要があります。 tween ノードは、コードで制御されたアニメーションに最適です。自作のアニメーションについては、 AnimationPlayer をご覧ください。
LifeBarにanimated_healthを割り当て¶
animated_health
変数はアニメーションされますが、実際の Bar
ノードと``Number`` ノードは更新されません。これを修正しましょう。
これまでのところ、update_healthメソッドは次のようになります:
func update_health(new_value):
tween.interpolate_property(self, "animated_health", animated_health, new_value, 0.6)
if not tween.is_active():
tween.start()
public void UpdateHealth(int health)
{
_tween.InterpolateProperty(this, "_animatedHealth", _animatedHealth, health, 0.6f, Tween.TransitionType.Linear,
Tween.EaseType.In);
if(!_tween.IsActive())
{
_tween.Start();
}
}
この例では、 number_label
はテキストを取るため、 _process
メソッドを使ってアニメーションする必要があります。前と同じように、 _process
内の Number
ノードと TextureProgress
ノードを更新します:
func _process(delta):
number_label.text = str(animated_health)
bar.value = animated_health
public override void _Process(float delta)
{
_numberLabel.Text = _animatedHealth.ToString();
_bar.Value = _animatedHealth;
}
注釈
number_label と bar は、 Number ノードと TextureProgress ノードへの参照を格納する変数です。
ゲームをプレイして、バーがスムーズにアニメーションされるのを確認します。しかし、テキストに小数点以下が表示され、混乱しているように見えます。また、ゲームのスタイルを考慮すると、ライフバーがより途切れやすい方法でアニメーションされるとよいでしょう。

アニメーションは滑らかですが、番号の表示が乱れています¶
animated_health
を四捨五入することで両方の問題を解決できます。 round_value
という名前のローカル変数を使用して、丸められた animated_health
を格納します。 それを number_label.text
と bar.value
に割り当てます:
func _process(delta):
var round_value = round(animated_health)
number_label.text = str(round_value)
bar.value = round_value
public override void _Process(float delta)
{
var roundValue = Mathf.Round(_animatedHealth);
_numberLabel.Text = roundValue.ToString();
_bar.Value = roundValue;
}
もう一度ゲームを実行すると、ブロックのようなアニメーションが表示されます。

animated_healthの四捨五入と文字列へ変換を一緒に行えば、一石二鳥です¶
ちなみに
プレイヤーがヒットするたびに、 GUI
は _on_Player_health_changed
を呼び出し、その _ on_Player_health_changed
は update_health
を呼び出します。これにより、アニメーションが更新され、 _ process
の number_label
と bar
が更新されます。健康状態が徐々に低下する、アニメーションするライフバーは、単に見た目上のトリックですが、それによってGUIが生き生きと感じられます。もしも Player
が3つのダメージを受けた場合、一瞬で発生します。
自機損失時にバーを消す¶
緑のキャラクタが消滅すると、消滅アニメーションが再生されて消えます。この時点では、インタフェースは表示されません。キャラクターが死んだら、バーも消去しましょう。複数のアニメーションを並行して管理するため、同じ Tween
ノードを再利用します。
まず、 GUI
は Player
の died
シグナルに接続して、いつ死亡したかを知る必要があります。 F1 を押して2Dワークスペースに戻ります。シーンドックで Player
ノードを選択し、インスペクタの隣にあるノードタブをクリックします。
died
シグナルを見つけて選択し、接続ボタンをクリックします。

シグナルにはすでに敵が接続しているはずです¶
シグナルの接続ウィンドウで、 GUI
ノードにもう一度接続します。ノードへのパスは ../../GUI
、ノード内のメソッドは _on_Player_died
と表示されます。関数作成オプションをオンのままにして、ウィンドウ下部の接続をクリックします。 これにより、スクリプトワークスペースの GUI.gd
ファイルに移動します。

これらの値は、シグナルの接続ウィンドウに表示されます¶
注釈
GUIが新しい情報を必要とするたびに、新しいシグナルを発信します。より多くのコネクションを追加すればするほど、それらを追跡するのは難しくなります。上手く使いましょう。
UI要素でフェードをアニメーションするには、その modulate
プロパティを使用する必要があります。 modulate
はテクスチャの色を乗算する Color
です。
注釈
modulate は CanvasItem クラスから取得され、すべての2DノードとUIノードはこれを継承します。ノードの表示/非表示を切り替え、シェーダを割り当て、 modulate で色を使用して修正することができます。
modulate
は、赤、緑、青、アルファの4つのチャンネルを持つ Color
値を取ります。最初の3つのチャネルのいずれかを暗くすると、インターフェイスが暗くなります。アルファチャンネルを下げると、インターフェイスがフェードアウトします。
2つのカラー値の間でTweenを行います。1つはアルファが 1
の白、つまり完全に不透明な状態、もう1つはアルファが 0
の完全に透明な白です。 _on_Player_die
メソッドの先頭に2つの変数を追加し、それぞれに start_color
と end_color
という名前を付けます。 Color()
コンストラクタを使用して、2つの Color
値を構築します。
func _on_Player_died():
var start_color = Color(1.0, 1.0, 1.0, 1.0)
var end_color = Color(1.0, 1.0, 1.0, 0.0)
public void OnPlayerDied()
{
var startColor = new Color(1.0f, 1.0f, 1.0f);
var endColor = new Color(1.0f, 1.0f, 1.0f, 0.0f);
}
Color(1.0、1.0、1.0)
は白に対応します。 4番目の引数、 start_color
と end_color
のそれぞれ 1.0
と 0.0
は、アルファチャンネルです。
次に、 Tween
ノードの interpolate_property
メソッドをもう一度呼び出す必要があります:
tween.interpolate_property(self, "modulate", start_color, end_color, 1.0)
_tween.InterpolateProperty(this, "modulate", startColor, endColor, 1.0f, Tween.TransitionType.Linear,
Tween.EaseType.In);
今回は、modulate
プロパティを変更し、start_color
から end_color
にアニメートします。継続時間は1秒で、線形に遷移します。完全な _on_Player_died
メソッドは次のとおりです:
func _on_Player_died():
var start_color = Color(1.0, 1.0, 1.0, 1.0)
var end_color = Color(1.0, 1.0, 1.0, 0.0)
tween.interpolate_property(self, "modulate", start_color, end_color, 1.0)
public void OnPlayerDied()
{
var startColor = new Color(1.0f, 1.0f, 1.0f);
var endColor = new Color(1.0f, 1.0f, 1.0f, 0.0f);
_tween.InterpolateProperty(this, "modulate", startColor, endColor, 1.0f, Tween.TransitionType.Linear,
Tween.EaseType.In);
}
以上です。これでゲームをプレイして最終結果を確認できます!

最終結果。上手くいきましたね!¶
注釈
まったく同じテクニックを使用して、プレイヤーが毒状態になったときにバーの色を変更したり、体力が低下したときにバーを赤にしたり、クリティカルヒットを受けたときにUIを振動させたりできます。基本は同じで、シグナルの発信により情報を Player から GUI に転送し、GUI に処理させます。