いんでぃーづ

ゲームいろいろ、いろいろ自由

Unity : グローバルなイベント通知の仕組み(EventBus)を自前実装する [C#]

例えば、敵を倒したタイミングで、プレイヤー、UI、他の敵が何らかのアクションを起こしたい場合、どのような経路でイベントを通知するか悩みます。

敵を各オブジェクトが監視したりするのは、参照が絡み合って管理がめんどうくさいです。

f:id:sugar_affordance:20170526120444j:plain

そこで便利なのがEventBusです。

元はAndroidなどに存在するライブラリです。
発生したイベントをグローバルなバス(伝送経路的な意味)を伝って、それを受け取りたいオブジェクト全部に伝えるというもの。

f:id:sugar_affordance:20170526120554j:plain

今回はこれを実装してみます。

EventBusクラスを作成

シングルトンクラス にします。
MonoBehaviourの継承は不要です。

・EventBus.cs
*ソース中Enemyクラスは敵につけているスクリプトとします

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EventBus {

    // シングルトンにする
    static EventBus instance;
    public static EventBus Instance {
        get {
            if (instance == null) {
                instance = new EventBus ();
            }
            return instance;
        }
    }
    private EventBus() {
    }

    // 通知受け取りデリゲート定義
    public delegate void OnDefeatEnemy(Enemy enemy);
    event OnDefeatEnemy _OnDefeatEnemy;


    // 通知受け取り登録
    public void Subscribe(OnDefeatEnemy onDefeatEnemy) {
        _OnDefeatEnemy += onDefeatEnemy;
    }

    // 通知受け取り解除
    public void Unsubscribe(OnDefeatEnemy onDefeatEnemy) {
        _OnDefeatEnemy -= onDefeatEnemy;
    }

    // 通知実行
    public void NotifyDefeatEnemy(Enemy enemy) {
        if (_OnDefeatEnemy != null)
            _OnDefeatEnemy (enemy);
    }
}

通知を受け取るオブジェクトで、下のように実装します。

public class Receiver : MonoBehaviour {

    void Start () {
        // 通知受け取り登録
        EventBus.Instance.Subscribe ((EventBus.OnDefeatEnemy)OnDefeatEnemy);
    }

    void OnDestroy() {
        // 通知受け取り解除
        EventBus.Instance.Unsubscribe ((EventBus.OnDefeatEnemy)OnDefeatEnemy);
    }

    void OnDefeatEnemy(Enemy enemy) {
        Debug.Log ("敵を倒した!");
    }
}

通知を送信する場合、倒された敵のスクリプトでNotifyを呼び出します。

public class Enemy : MonoBehaviour {

    void OnDefeated() {
        // 敵を倒した通知
        EventBus.Instance.NotifyDefeatEnemy (this);
    }
}

利点

まずMonoBehaviourを継承していないので、シーン内にオブジェクトとして置いておく必要がありません
また、通知を受け取りたいオブジェクト内で登録と解除を完結できるので、オブジェクト間の依存性を極力減らすことができます。 新しい種類の通知を受け取りたい場合、デリゲートを増やしていけばいくらでも対応可能です。 (ファイル肥大化が嫌ならこちらを参考に)

注意

Unsubscribeしないでオブジェクト削除された場合、削除されたはずのスクリプトの関数が呼ばれてしまうので、ちゃんと後処理はやりましょう。
Subscribe 時に関数をキャストしていますが、やらなかったらどうなるかは...おたのしみに。

もっとうまく実装できないの?

Dictionary<System.Type, System.Delegate> eventTable = new Dictionary<System.Type, System.Delegate>();
    
public void Subscribe(OnEvent observer) {
    eventTable[typeof(OnEvent)] = (OnEvent)eventTable[typeof(OnEvent)] + observer;
}

みたいにディクショナリで管理できるぽい? けど試してない

方法 : ディクショナリを使用してイベント インスタンスを格納する (C# プログラミング ガイド) | Microsoft Docs


同様のアセットがGitHabに公開されていますが、こっちは GameObject の配置が必要なのが微妙かな。
Unityはいちいち GameObject 配置必要問題に当たるので面倒ですね。

GitHub - mattak/Unibus: Unibus is event passing system for Unity3D. 🚀

図の素材をお借りしました。

【Rド】→RPG2003ドット絵素材


“Unity” and Unity logos are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere, and are used under license.


免責事項

当サイトの広告バナー、リンクによって提供される情報、サービス内容について、当サイトは一切の責任を負いません。

また、当サイトの情報を元にユーザ様が不利益を被った場合にも、当サイトは一切の責任を負いません。

すべて自己責任でお願いします。