消息派发对项目中各个功能模块的解耦非常有帮助,免去模块间复杂的调用.
但unity自带的消息函数SendMessage效率一直都不高,所以基本上项目中都很少用它,而是自己写一套消息派发.
消息派发原理简单:订阅发布模式.
各模块需要注意哪些消息就订阅哪些消息[比如我定了报纸,而我邻居定了牛奶]
消息派发者会保存对应的订阅者信息[这个消息派发企业业务很广泛,既有报纸也有牛奶]
业务逻辑中需要触发该消息时,消息派发者就会检索我的订阅信息看看有没有模块订阅了该消息,有那么触发这个模块的处理函数[派出快递员,敲你大门,把报纸牛奶给你]
实现方式大致有2种:
1.
->写消息派发类,该类作为单例确保全局调用,它包含 订阅函数 取消订阅函数 和 派发函数 . 以及存储订阅信息的数据结构:Dictionary
->写一个接口类, 如 IMsgHandler , 它声明 订阅者模块处理消息的函数格式. 如: public bool OnMsgHandler(string msgName,param object[] args);
->订阅者继承接口类,订阅消息并重写IMsgHandler接口实现自己的业务逻辑 .
2.
->写消息派发类,该类作为单例确保全局调用,它包含 订阅函数 取消订阅函数 和 派发函数 . 以及存储订阅信息的数据结构Dictionary
->订阅者继承接口类,注册订阅消息函数,实现自己的业务逻辑 .
这两种方式的区别在于第一种存储的是对象 ,另一种存储的是函数引用.
存储对象:派发时 遍历字典中订阅的对象,然后调用消息处理接口函数,所有的逻辑都在一个函数中 比较集中.
存储函数引用:派发时 遍历字典中订阅的函数引用,直接调用该引用, 调用相对分散但方便
下面贴代码:
第一种:
接口类[IMsgReceiver.cs]
public interfaceIMsgReceiver{
// msgName:消息名 , args:参数 , 注意返回值 如果返true 表示该消息已经被截断 消息派发不会再向下派发
bool OnMsgHandler(string msgName,params object[] args);
}
消息派发类(单例)[MsgDispatcher.cs]
public class MsgDispatcher{
private Dictionary> m_Msgs = new Dictionary>();
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 list = new List();
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 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,"哈哈哈哈");
}
}