1. 程式人生 > >Unity中訊息派發系統的總結(1)

Unity中訊息派發系統的總結(1)

訊息派發對專案中各個功能模組的解耦非常有幫助,免去模組間複雜的呼叫.

但unity自帶的訊息函式SendMessage效率一直都不高,所以基本上專案中都很少用它,而是自己寫一套訊息派發.

 

訊息派發原理簡單:訂閱釋出模式.

各模組需要注意哪些訊息就訂閱哪些訊息[比如我定了報紙,而我鄰居定了牛奶]

訊息派發者會儲存對應的訂閱者資訊[這個訊息派發企業業務很廣泛,既有報紙也有牛奶]

業務邏輯中需要觸發該訊息時,訊息派發者就會檢索我的訂閱資訊看看有沒有模組訂閱了該訊息,有那麼觸發這個模組的處理函式[派出快遞員,敲你大門,把報紙牛奶給你] 

 

實現方式大致有2種:

1.

->寫訊息派發類,該類作為單例確保全域性呼叫,它包含 訂閱函式 取消訂閱函式 和 派發函式 . 以及儲存訂閱資訊的資料結構:Dictionary<string,List<IMsgHandler>>

->寫一個介面類, 如 IMsgHandler , 它宣告 訂閱者模組處理訊息的函式格式. 如: public bool OnMsgHandler(string msgName,param object[] args);

->訂閱者繼承介面類,訂閱訊息並重寫IMsgHandler介面實現自己的業務邏輯 .

 

2.

->寫訊息派發類,該類作為單例確保全域性呼叫,它包含 訂閱函式 取消訂閱函式 和 派發函式 . 以及儲存訂閱資訊的資料結構Dictionary<string,UnityAction<T>>

->訂閱者繼承介面類,註冊訂閱訊息函式,實現自己的業務邏輯 .

 

這兩種方式的區別在於第一種儲存的是物件 ,另一種儲存的是函式引用.

儲存物件:派發時 遍歷字典中訂閱的物件,然後呼叫訊息處理介面函式,所有的邏輯都在一個函式中 比較集中. 

儲存函式引用:派發時 遍歷字典中訂閱的函式引用,直接呼叫該引用, 呼叫相對分散但方便

 

下面貼程式碼:

第一種:

介面類[IMsgReceiver.cs]

public interfaceIMsgReceiver{
    // msgName:訊息名 , args:引數 , 注意返回值 如果返true 表示該訊息已經被截斷 訊息派發不會再向下派發
    bool OnMsgHandler(string msgName,params object[] args);
}

 

 

訊息派發類(單例)[MsgDispatcher.cs]

public class MsgDispatcher{
    private Dictionary<string ,List<IMsgRecevier>> m_Msgs = new Dictionary<string,List<IMsgReceiver>>();  
    
    private static MsgDispatcher m_Ins;
    public static MsgDispatcher GetInstance(){
            if(m_Ins==null){
                m_Ins = new MsgDispatcher();
            }
            return m_Ins;
        }


        //訂閱訊息
        public void Subscribe(string msg, IMsgReceiver recevier) {
            if (recevier == null) {
                Debug.LogError("SubscribeMsg : recevier == null");
                return;
            }

            if (Check(msg, recevier)) {
                Debug.LogFormat("SubscribeMsg: recevier has been subscribed ,msg={0},recevier={1}", msg, recevier.ToString());
            } else {
                if (this.m_Msgs.ContainsKey(msg)) {
                    this.m_Msgs[msg].Add(recevier);
                } else {
                    List<IMsgReceiver> list = new List<IMsgReceiver>();
                    list.Add(recevier);
                    this.m_Msgs.Add(msg, list);
                }
            }
        }

        // 取消訂閱訊息
        public void UnSubscribe(string msg, IMsgReceiver recevier) {
            if (recevier == null) {
                Debug.LogError("UnSubscribeMsg: recevier == null");
                return;
            }

            if (Check(msg, recevier)) {
                this.m_Msgs[msg].Remove(recevier);
            }
        }

        public void UnSubscribe(IMsgReceiver recevier) {
            if(recevier == null) {
                Debug.LogError("UnSubscribeMsg: recevier == null");
                return;
            }
            foreach(var iter in m_Msgs) {
                iter.Value.Remove(recevier);
            }
        }

        //檢查訂閱
        public bool Check(string msg, IMsgReceiver recevier) {
            if (m_Msgs.ContainsKey(msg)) {
                var list = m_Msgs[msg];
                return list.Contains(recevier);
            }
            return false;
        }

        //清除
        public void ClearAll() {
            m_Msgs.Clear();
        }

        //丟擲訊息
        public void Fire(string msg, params object[] args) {
            if (!this.m_Msgs.ContainsKey(msg)) {
                Debug.LogWarning("Fire msg: msg has no receiver!");
                return;
            }

            Debug.Log("[MsgDispatcher] fire msg:"+msg);

            List<IMsgReceiver> list = this.m_Msgs[msg];
            try {
                for (int i = 0; i < list.Count; ++i) {
                    if (list[i] != null) {
                        bool bNext = list[i].OnMsgHandler(msg, args);
                        if (bNext) //有返回true的 訊息會被截斷,下面的handler不會接受到該訊息
                            {
                            Debug.LogWarningFormat("Fire msg: msg[{0}] has been stop fire!", msg);
                            break;
                        }
                    }
                }
            } catch {

            }
        }
  
}

 

最後來看看業務邏輯類如何使用.

比如 玩家類(Player.cs) 他需要訂閱牛奶訊息(Msg_Milk)

先宣告牛奶的訊息[GameEvents.cs]

public static class GameEvents{
    public const string Msg_Milk = "Msg_Milk";    //牛奶訊息
}

 

 

玩家類[Player.cs]

public class Player:MonoBehaviour,IMsgReceiver{
    
    //訂閱牛奶訊息
    void OnEnable(){
        MsgDispatcher.GetInstance().Subscribe(GameEvents.Msg_Milk,this);
    }
    
    //取消訂閱牛奶訊息
    void OnDisable(){
        MsgDispatcher.GetInstance().UnSubscribe(GameEvents.Msg_Milk,this);
    }

    //重寫訊息處理函式
    public bool OnMsgHandler(string msgName,params object[] args){
            switch(msgName){
                case GameEvents.Msg_Milk:
                        return onMsgMilk((string)args[0]);
            }
            return false;
        }
        
        // 真正處理milk訊息的函式
        bool onMsgMilk(string content){
               Debug.Log("Player收到milk訊息,引數:"+content);
                return false; 
        }
}

 

 

最後的最後寫一個測試類3秒後觸發牛奶訊息[Test.cs]

public class Test:MonoBehaviour{
    void Awake(){{
        StartCoroutine(triggerMsg());
    }

    IEnumerator triggerMsg(){
    yield return new WaitForSeconds(3f);
    //根據你的業務邏輯傳入不同引數
    MsgDispatcher.GetInstance().Fire(GameEvents.Msg_Milk,"哈哈哈哈");    
  }
}