コードを使用してゲームのUIを制御する

イントロ

このチュートリアルでは、キャラクターをライフバーに接続し、その健康状態の表示をライブバーで行います。

../../_images/lifebar_tutorial_final_result.gif

ここで作成するのは、キャラクターがヒットを受けたときにバーとカウンターをアニメーションさせるものです。キャラクターが死ぬと徐々に消えます。

次の方法について学習します:

  • シグナルを使ってキャラクターをGUIに 接続 する方法
  • GDscriptを使用してGUIを 制御する 方法
  • Tweenノードを使ってライフ バーをアニメーション化する方法

代わりにインターフェイスを設定する方法を学びたい場合は、ステップバイステップのUIチュートリアルを参照してください:

ゲームをコーディングするときには、最初にゲームプレイのコアとなる部分、つまり動作のメカニズム、プレイヤーの入力、勝敗条件を構築しなければなりません。その後にUIを設計します。可能であれば、プロジェクトを構成するすべての要素を別々にしておきます。各キャラクタは独自のシーンの中に、独自のスクリプトを持って存在する必要があり、UI要素もそれと同様です。これによってバグを防ぎ、プロジェクトを管理しやすくし、異なるチームメンバーがゲームのそれぞれの部分で作業できるようになります。

中核となるゲームプレイとUIの準備ができたら、何らかの方法でそれらを接続する必要があります。この例では、一定の時間間隔でプレイヤーを攻撃する敵がいます。プレイヤーがダメージを受けたときは、ライフバーには更新してほしいでしょう。

これを行うには、 シグナル を使用します。

注釈

シグナルは、ObserverパターンのGodot版です。メッセージを送信することができます。他のノードは、シグナルを 発信 するオブジェクトに接続して、情報を受信できます。これは強力なツールで、ユーザーインターフェイスや実績システムで多用されています。ただし、全ての場面で使用する必要はありません。 2つのノードを接続すると、ノード間の結合度が増します。接続が多いと、それだけ管理するのが難しくなります。詳細については、GDquestの signals video tutorial(英語)をご覧ください。

スタートプロジェクトをダウンロードして参照する

Godotプロジェクトをダウンロード(ui_code_life_bar.zip)してください。チュートリアルに必要なすべての素材やスクリプトが入っています。zipアーカイブを解凍して、2つのフォルダ startend を取得します。

Godotに start プロジェクトを読み込みます。 ファイルシステム ドックで、LevelMockup.tscnをダブルクリックして開きます。 2人のキャラクターが向かい合っているRPGゲームのモックアップです。ピンク色の敵は、死ぬまで一定の時間間隔で緑色の正方形を攻撃し、ダメージを与えます。ゲームを試してみてください: 基本的な戦闘の仕組みはすでに機能しています。ですが、キャラクターはライフバーに接続されていないため、GUI は何もしません。

注釈

これは、ゲームのコーディング方法の典型です。まず最初にゲームプレイのコアを実装し、プレイヤー死亡の処理ができてから、ようやくインターフェイスを追加します。なぜなら、UIはゲーム内で起こっていることに耳を傾けているからです。なので、他のシステムがまだ存在していない段階では動きません。ゲームプレイのプロトタイプを作成してテストする前にUIを設計しても、多分うまく機能せずに最初からつくり直すはめになるでしょう。

シーンには、背景スプライト、GUI、および2つのキャラクターが含まれています。

../../_images/lifebar_tutorial_life_bar_step_tut_LevelMockup_scene_tree.png

シーンツリー (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」はそれ自体サブシーンです。

../../_images/lifebar_tutorial_Player_with_editable_children_on.png

シーンツリー (Playerシーンの子を表示するように設定)

プレイヤーのmax_healthを使用してライフバーをセットアップする

プレイヤーの現在の状態を何らかの方法でGUIに伝え、ライフバーのテクスチャを更新し、残りの体力を画面左上のHPカウンターに表示する必要があります。そのために、プレイヤーがダメージを受けるたびに、プレイヤーの体力をGUIに送信します。GUIは、この値で Lifebar ノードと Number ノードを更新します。

ここで数字を表示させることもできますが、その前に、正しい比率で更新するため、バーの max_value を初期化する必要があります。なので最初のステップは、緑のキャラクターの max_healthGUI に伝えることです。

ちなみに

TextureProgress であるバーは、デフォルトで max_value100 になっています。 キャラクターの体力を数字で表示する必要がない場合は、その max_value プロパティを変更する必要はありません。代わりに、 Player から GUI にパーセンテージを送信します: health / max_health * 100

../../_images/lifebar_tutorial_TextureProgress_default_max_value.png

シーンドック内の GUI の右側にあるスクリプトアイコンをクリックして、スクリプトを開きます。 _ready 関数にて、プレイヤーの max_health を新しい変数に格納し、それを使って barmax_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 はゲーム開始時に healthmax_health に設定するので、このまま作業できます。なぜ max_health を使用しているのでしょう? これには 2つの理由があります:

health が常に max_health と等しい保証はありません。このゲームの将来バージョンでは、プレイヤーが体力をいくらか失った状態でも、次のレベルを読み込めるようにするかもしれません。

注釈

ゲーム内でシーンを開くと、Godotはシーンドック内の順序に従って、上から下にノードを1つずつ作成していきます。 GUIPlayer は、同じノードブランチの一部ではありません。これらが相互にアクセスするときに両方とも存在することを確認するには、 _ready 関数を使用する必要があります。Godotは、すべてのノードをロードした直後、ゲームを開始する前に _ready を呼び出します。これは、すべてを設定してゲームセッションを準備するには最適な関数です。_ready の詳細についてはスクリプト(続き)を参照して下さい。

プレイヤーがヒットしたときにシグナルで体力を更新する

GUIは Player から health の更新値を受け取る準備ができています。これを実現するために、シグナルを使用します。

注釈

たくさんの便利な組み込みシグナルがあり、たとえば全てのノードが持つenter_treeexit_tree は、それぞれ作成されたときと破棄されたときに発信します。signal キーワードを使用すれば、独自のシグナルを作成することもできます。 Player ノードには、そうして用意された2つのシグナル diedhealth_changed がすでにあります。

_process 関数内で Player ノードを直接取得し、体力値を確認するのはどうでしょう? この方法でノードにアクセスすると、ノード間に緊密な結合ができてしまいます。これは控えめにやれば、うまくいくかもしれません。しかし、ゲームが大きくなるにつれて、より多くの接続をすることになるはずです。この方法でノードを取得して行くと、すぐに複雑になってしまいます。問題はそれだけではありません: _process 関数内で、常に他のノードの状態変化を確認する必要があります。このチェックは(60FPSのゲームなら)1秒間に60回行われ、コードには実行順序があるので恐らくゲームが中断されることになるでしょう。

特定のフレームで、更新「前」に別のノードのプロパティを参照すると、その直前のフレームから値が取得されます。これは、修正が難しい不明瞭なバグにつながります。一方、シグナルは変化が起こった直後に発せられます。新しい情報を 確実 に入手できるのです。また、変更が行われた「直後」に、接続されたノードの状態を更新します。

注釈

Observerパターンは、シグナルの基盤ですが、ノードのブランチ間に若干の結合を追加します。しかし大抵の場合、2つの異なるクラス間での通信においては、ノードに直接アクセスするよりも軽量で安全です。親ノードから子の値を取得することは問題ありません。しかし、2つの別々のブランチをまたいで作業しているなら、シグナルを優先したほうがいいでしょう。Observerパターンの詳細については、Game Programming Patternsの Observer pattern (英語)を参照してください。完全版の本はオンラインで無料で入手できます。

これを念頭に置いて、 GUIPlayer に接続しましょう。まずシーンドックの Player ノードをクリックして選択します。インスペクタに移動し、「ノード」タブをクリックします。ここは、選択したノードからのシグナルを受信待機させるために、ノードを接続する場所です。

最初のセクションでは、 Player.gd で定義されているカスタムシグナルを一覧表示します:

  • キャラクターが死んだときには died が出力されます。 このシグナルは後で、UIを非表示にするために使用します。
  • health_changed は、キャラクタがヒットを受けたときに放出されます。
../../_images/lifebar_tutorial_health_changed_signal.png

これから「health_changed」シグナルに接続します

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

../../_images/lifebar_tutorial_connect_signal_window_health_changed.png

GUIノードが選択された「シグナル接続」ウィンドウ

ちなみに

必要に応じて、コード内でノードを接続できます。ただし、エディタから実行することには次の利点2つがあります:

  1. Godotはあなたに代わって、接続されたスクリプト内に新しいコールバック関数を書けます
  2. シーンドック内で、信号を発信するノードの横に発信元アイコンが表示されます

ウィンドウの下部に、選択したノードへのパスが表示されます。"Method in Node"と呼ばれる 2 行目に注目して下さい。これは、シグナルが出力されるときに呼び出される GUI ノード上のメソッドです。このメソッドは、シグナルと共に送信された値を受け取り、それらを処理できるようにします。右側を見ると、デフォルトで「関数を作成」ラジオボタンがオンになっています。ウィンドウの下部にある接続ボタンをクリックします。Godot は GUI ノード内にメソッドを作成します。スクリプトエディタが開き、新しい _on_Player_health_changed 関数内にカーソルが置かれます。

注釈

エディタからノードを接続すると、Godotは _on_EmitterName_signal_name (_on_(発信元名)_(シグナル名)) のパターンでメソッド名を生成します。すでにそのメソッドが書かれている場合は、「関数を作成」オプションが有効であっても内容は保持されます。この名前は任意の名前に置き換えることができます。

../../_images/lifebar_tutorial_godot_generates_signal_callback.png

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#命名規則に従うものです。

../../_images/lifebar_tutorial_player_gd_emits_health_changed_code.png

Player.gdでは、Playerがhealth_changedシグナルを発信すると、その体力値も送信します

_on_Player_health_changed 内で、 update_health と呼ばれる2番目の関数を呼び出して、 player_health 変数を渡します。

注釈

`LifeBar`と`Number`の体力値を直接更新できます。 代わりにこの方法を使用する理由は2つあります:

  1. この名前は、プレイヤーがダメージを受けたときに GUI の体力(health)カウントを更新することを、将来の自分自身やチームメイトのために明確にします
  2. この方法は少し後で再利用します

_on_Player_health_changed の下に新しい update_health メソッドを作成します。唯一の引数としてnew_valueを取ります:

func update_health(new_value):
    pass
public void UpdateHealth(int health)
{
}

このメソッドは以下を行う必要があります:

  • Number ノードの text を文字列に変換された new_value に設定します
  • TextureProgressvaluenew_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 は、ほぼすべての値をテキストに変換する組み込み関数です。 Numbertext プロパティには文字列が必要なので、それを new_value に直接割り当てることはできません

また、_ready 関数の最後で update_health を呼び出して、ゲームの開始時に適切な値で Number ノードの text を初期化します。F5 を押してゲームをテストします。攻撃を受けるたびにライフバーが更新されます!

../../_images/lifebar_tutorial_LifeBar_health_update_no_anim.gif

Playerがヒットすると、NumberノードとTextureProgressの両方が更新されます

Tweenノードを使用した、自機損失のアニメーション

このインターフェイスは機能していますが、いくつかのアニメーションを使用することができます。これは、プロパティをアニメーション化するために不可欠なツールである Tween ノードを導入する良い機会です。 Tween は、一定の期間にわたって開始状態から終了状態まで、必要なものをアニメーション化します。たとえば、キャラクタがダメージを受けたときに、 TextureProgress の体力(health)を現在のレベルから Player の新しい health にアニメートできます。

GUI シーンには、 tween 変数に格納されている Tween 子ノードが既に含まれています。それでは、それを使用してみましょう。 update_health にいくつかの変更を加える必要があります。

Tween ノードの interpolate_property メソッドを使用します。引数は7つあります:

  1. アニメーションするプロパティを所有するノードへの参照
  2. 文字列としてのプロパティの識別子
  3. 開始値
  4. 終了値
  5. アニメーションの継続時間(秒)
  6. トランジションのタイプ
  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_healthself 、つまり GUI ノードをターゲットにします。 Tweeninterpolation_property は、プロパティの名前を文字列として取得します。 ですので "animated_health" とします。

... _health", animated_health, new_value, 0.6 ...

開始点は、バーの現在の値です。この部分はまだコーディングする必要がありますが、 animated_health になります。アニメーションの終了点は、 health_changed 後の Playerhealth 、つまり new_value です。 0.6 はアニメーションの秒単位の長さです。

tween.start()Tween ノードをアクティブにするまでアニメーションは再生されません。ノードがアクティブでない場合は、これを一度だけ実行する必要があります。 最後の行の後にこのコードを追加します:

if not tween.is_active():
    tween.start()
if (!_tween.IsActive())
{
    _tween.Start();
}

注釈

Playerhealth プロパティをアニメートすることはできますが、そうすべきではありません。キャラクターは命中するとすぐにライフを失います。これにより、いつ死亡したかを知りたいなど、状態を管理しやすくなります。アニメーションは常に別のデータ コンテナーまたはノードに格納する必要があります。 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_labelbar は、 Number ノードと TextureProgress ノードへの参照を格納する変数です。

ゲームをプレイして、バーがスムーズにアニメーションされるのを確認します。しかし、テキストに小数点以下が表示され、混乱しているように見えます。また、ゲームのスタイルを考慮すると、ライフバーがより途切れやすい方法でアニメーションされるとよいでしょう。

../../_images/lifebar_tutorial_number_animation_messed_up.gif

アニメーションは滑らかですが、番号の表示が乱れています

animated_health を四捨五入することで両方の問題を解決できます。 round_value という名前のローカル変数を使用して、丸められた animated_health を格納します。 それを number_label.textbar.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;
}

もう一度ゲームを実行すると、ブロックのようなアニメーションが表示されます。

../../_images/lifebar_tutorial_number_animation_working.gif

animated_healthの四捨五入と文字列へ変換を一緒に行えば、一石二鳥です

ちなみに

プレイヤーがヒットするたびに、 GUI_on_Player_health_changed を呼び出し、その _ on_Player_health_changedupdate_health を呼び出します。これにより、アニメーションが更新され、 _ processnumber_labelbar が更新されます。健康状態が徐々に低下する、アニメーションするライフバーは、単に見た目上のトリックですが、それによってGUIが生き生きと感じられます。もしも Player が3つのダメージを受けた場合、一瞬で発生します。

自機損失時にバーを消す

緑のキャラクタが消滅すると、消滅アニメーションが再生されて消えます。この時点では、インタフェースは表示されません。キャラクターが死んだら、バーも消去しましょう。複数のアニメーションを並行して管理するため、同じ Tween ノードを再利用します。

まず、 GUIPlayerdied シグナルに接続して、いつ死亡したかを知る必要があります。 F1 を押して2Dワークスペースに戻ります。シーンドックで Player ノードを選択し、インスペクタの隣にあるノードタブをクリックします。

died シグナルを見つけて選択し、接続ボタンをクリックします。

../../_images/lifebar_tutorial_player_died_signal_enemy_connected.png

シグナルにはすでに敵が接続しているはずです

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

../../_images/lifebar_tutorial_player_died_connecting_signal_window.png

これらの値は、シグナルの接続ウィンドウに表示されます

注釈

GUIが新しい情報を必要とするたびに、新しいシグナルを発信します。より多くのコネクションを追加すればするほど、それらを追跡するのは難しくなります。上手く使いましょう。

UI要素でフェードをアニメーションするには、その modulate プロパティを使用する必要があります。 modulate はテクスチャの色を乗算する Color です。

注釈

modulateCanvasItem クラスから取得され、すべての2DノードとUIノードはこれを継承します。ノードの表示/非表示を切り替え、シェーダを割り当て、 modulate で色を使用して修正することができます。

modulate は、赤、緑、青、アルファの4つのチャンネルを持つ Color 値を取ります。最初の3つのチャネルのいずれかを暗くすると、インターフェイスが暗くなります。アルファチャンネルを下げると、インターフェイスがフェードアウトします。

2つのカラー値の間でTweenを行います。1つはアルファが 1 の白、つまり完全に不透明な状態、もう1つはアルファが 0 の完全に透明な白です。 _on_Player_die メソッドの先頭に2つの変数を追加し、それぞれに start_colorend_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_colorend_color のそれぞれ 1.00.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);
}

以上です。これでゲームをプレイして最終結果を確認できます!

../../_images/lifebar_tutorial_final_result.gif

最終結果。上手くいきましたね!

注釈

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