Menu
CoddyTech

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やシングルトンなど、知っている方法で見つけます。オブジェクトが破棄されるようになったら、-=でリスナーの購読を解除することが重要になります。これは次のレッスンで扱います。

challenge icon

チャレンジ

簡単

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;
            }
        }
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Unityにおけるゲームアーキテクチャのすべてのレッスン