UnityにおけるC#イベント
CoddyのUnityジャーニー「Unityにおけるゲームアーキテクチャ」セクションの一部。レッスン 10/50。
スコアが変わると、いくつかの要素がその変化を気にします。スコア表示のテキスト、サウンド、実績などです。ScoreKeeperがそれぞれを呼び出す場合、そのすべてに対応するフィールドが必要になり、新しい機能を追加するたびにScoreKeeperを編集することになります。イベントを使うと、この関係が逆になります。ScoreKeeperが変更を通知し、その変更を必要とする側が購読します。C#のeventとActionは知っていますね。Unityでは、発行側とリスナーはコンポーネントで、通常は異なるGameObjectに配置されます。
using System;
using UnityEngine;
public class ScoreKeeper : MonoBehaviour
{
public event Action<int> OnScoreChanged;
private int score;
public void AddPoints(int points)
{
score += points;
OnScoreChanged?.Invoke(score);
}
}// ScoreLabel.cs、HUDのテキスト上に配置
public ScoreKeeper keeper;
public TMP_Text label;
void Start()
{
keeper.OnScoreChanged += ShowScore;
}
void ShowScore(int score)
{
label.text = "Score: " + score;
}Action<int>は、各リスナーがintを1つ受け取るメソッドであることを意味します。この場合、そのintは新しいスコアです。Invokeは、購読した順に、購読しているすべてのメソッドを呼び出します。リスナーがいない場合、イベントはnullなので、通常のOnScoreChanged(score)ではNullReferenceExceptionが発生しますが、?.を使うと呼び出しがスキップされます。eventキーワードにより、他のスクリプトで使えるのは+=と-=に限られます。ScoreLabelでは、keeper.OnScoreChanged(5)やkeeper.OnScoreChanged = nullはコンパイルできません。
ScoreKeeperにはラベル用のフィールドがなく、ラベルの存在も知りません。コンボカウンターやサウンドプレイヤーも同じ方法で購読できます。ScoreKeeperはそのままで、HUDのないテストシーンもエラーなく実行できます。ここで示したように、リスナーはInspectorの参照を通じて発行側を見つけるか、GetComponentやシングルトンなど、知っている方法で見つけます。オブジェクトが破棄されるようになったら、-=でリスナーの購読を解除することが重要になります。これは次のレッスンで扱います。
チャレンジ
簡単Spaceキーを押して攻撃するたびにファイターのコンボ数が増え、攻撃せずにcomboWindow(1)秒が経過すると0に戻ります。現在、Fighter.csがコンボのテキストと炎のサイズを直接設定しているため、それぞれのフィールドが必要です。イベントを使う形に変更しましょう:
Fighter.csで、コンボ数(int)を渡すOnComboChangedという名前のpublicイベントを宣言します。コンボ数が変わるたびに、新しいコンボ数を使ってイベントを発生させます。0へのリセットも含め、誰もイベントを購読していないときに攻撃してもエラーが発生しないようにしてください。- Fighterの
comboTextとflameフィールド、およびそれらを使う行を削除します。FighterはHUDについて何も知りません。 ComboText.csとComboFlame.csで、Start()内にあるfighterのイベントにそれぞれのShowメソッドとGrowメソッドを登録します。どちらにもfighterフィールドがあり、シーン内でリンクされています。
Playを押し、Spaceキーでバッグを数回攻撃してから1秒待ち、Checkを押してください。
自分で試してみよう
using UnityEngine;
using UnityEngine.InputSystem;
using TMPro;
public class Fighter : MonoBehaviour
{
public float comboWindow = 1f;
public TMP_Text comboText;
public Transform flame;
private int combo;
private float sinceHit;
void Update()
{
if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
combo++;
sinceHit = 0f;
comboText.text = "Combo x" + combo;
flame.localScale = Vector3.one * (0.5f + 0.25f * combo);
}
else if (combo > 0)
{
sinceHit += Time.deltaTime;
if (sinceHit > comboWindow)
{
combo = 0;
comboText.text = "";
flame.localScale = Vector3.one * 0.5f;
}
}
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。