← logs に戻る
2026/07/01 · 4 min read ⚙️ intermediate

Unity のイベント3種を使い分ける(Observer パターン)

疎結合の要になる Observer パターンを、Unity の3つの実現方法——C# event / UnityEvent / ScriptableObject イベント——で比較し、使い分けの基準を示す。

「HP が減ったら UI を更新する」のように、通知する側が受け取る側を直接知らずに済ませるのが Observer パターンです。Unity にはこれを実現する方法が3つあり、それぞれ得意が違います。この記事では C# event / UnityEvent / ScriptableObject イベントを比較し、使い分けの基準を示します。

3つの選択肢

まず全体像です。同じ「通知」でも、コードで完結させたいのか、デザイナーが Inspector で繋ぎたいのか、シーンやプレハブを跨いで疎結合にしたいのかで選択が変わります。

方法繋ぐ場所向く場面
C# eventコードのみ開発者内で完結、最速・最軽量
UnityEventInspectorデザイナー連携、プレハブ内の配線
ScriptableObject イベントアセットシーン/プレハブを跨ぐ疎結合

C# event —— まずこれ

純粋な C# の event / delegate がそのまま使えます。最速・最軽量で、開発者間で閉じるなら第一候補です。

public class Health : MonoBehaviour
{
    public event Action<int> OnDamaged;      // 通知する側

    public void Damage(int amount)
    {
        OnDamaged?.Invoke(amount);           // 購読者を知らずに通知
    }
}

public class HealthUI : MonoBehaviour
{
    [SerializeField] Health health;

    void OnEnable()  => health.OnDamaged += Refresh;
    void OnDisable() => health.OnDamaged -= Refresh;   // 解除を忘れない
    void Refresh(int amount) { /* UI 更新 */ }
}

⚠️ 購読解除を忘れるとリークする。破棄済みオブジェクトが購読に残り、通知のたびに例外や多重実行を招く。OnEnable/OnDisable+=-= を対にするのが鉄則。

🧭 これは純粋な C# の event そのもの。Unity 特有なのは「解除をオブジェクトの寿命に紐づける」点で、UniRx の AddTo や UniTask のキャンセルと同じ発想(UniRx 入門)。

UnityEvent —— Inspector で繋ぐ

UnityEvent はシリアライズ可能で Inspector に表示され、コードを書かずに配線できます。デザイナーやレベルデザイナーとの連携に向きます。

using UnityEngine.Events;

public class Button : MonoBehaviour
{
    [SerializeField] UnityEvent onPressed;   // Inspector に一覧で出る

    void Press() => onPressed.Invoke();
}

💡 C# event より低速でアロケーションもある。毎フレーム大量に呼ぶ用途には向かない。「たまに起きるイベントを Inspector で繋ぐ」のが得意分野。

🧭 UnityEvent は純粋な C# に対応物がない、Unity 独自のシリアライズ可能なイベント。デザイナーが GUI で購読先を指定できるのが最大の利点。

ScriptableObject イベント —— 跨いで繋ぐ

イベントを ScriptableObject アセットとして作り、それをチャンネルにする方式です。通知側も購読側も「そのアセット」だけを参照するので、互いを直接参照せずシーンやプレハブを跨いで繋がります。

[CreateAssetMenu(menuName = "Events/GameEvent")]
public class GameEvent : ScriptableObject
{
    readonly List<Action> listeners = new();

    public void Raise() { foreach (var l in listeners) l(); }
    public void Register(Action l)   => listeners.Add(l);
    public void Unregister(Action l) => listeners.Remove(l);
}

PlayerDied のようなアセットを1つ作れば、敵・UI・サウンドが互いを知らずに同じイベントへ集まれます。プレハブ単体でも成立するので、大きめのプロジェクトの疎結合に効きます。

⚠️ ここでも Register / Unregister の対は必須。ScriptableObject はシーンをまたいで生き続けるため、解除漏れは C# event 以上に尾を引く。

使い分けのまとめ

  • Observer パターンは「通知側が購読側を直接知らない」疎結合の基本形。Unity では実現方法が3つある。
  • C# event: 最速・最軽量。開発者内で完結するならまずこれ。OnEnable/OnDisable で解除を対にする。
  • UnityEvent: Inspector で配線でき、デザイナー連携に強い。低速なので高頻度呼び出しには不向き。
  • ScriptableObject イベント: アセットをチャンネルにし、シーン/プレハブを跨いで疎結合にできる。中〜大規模向け。
  • どれも共通して 購読解除をオブジェクトの寿命に紐づけるのが要。

次にやること: このシリーズの他パターン(Singleton / Object Pool / State machine)と組み合わせると、状態変化を ScriptableObject イベントで放送する、といった構成に発展できる。

← logs 一覧 tags →