什么是消息机制
可以理解为:一个物体发出消息,另外一个或几个物体会接收到这条消息并作出相应的处理。这样做的好处就是模块之间相互独立,降低了模块之间的耦合度,每个脚本都可以有收发消息的能力,把模块之间的调用转化成了收发消息来实现。当然,一个脚本如果要对某个消息做出反应,前提是要添加对该消息的订阅。在消息框架中,通常会使用字典或者链表等数据结构来保存维护所有消息及对应的消息订阅者。
Unity自带的SendMessage
unity自带的消息发送机制SendMessage其实是一种伪监听者模式,它利用的是反射机制,它的效率是很低的,每次调用的时候都会去遍历它自身或者父节点、子节点上对应的方法,因此我们基本不回去用自带的SendMessage。
我们的消息机制框架图
框架主要类
MonoBase:
public class MonoBase : MonoBehaviour { public virtual void Execute(int eventCode, object message) { } }
ManagerBase:
public class ManagerBase : MonoBase { /// <summary> /// 处理自身的消息 /// </summary> /// <param name="eventCode">Event code.</param> /// <param name="message">Message.</param> public override void Execute(int eventCode, object message) { if (!dict.ContainsKey(eventCode)) { Debug.LogWarning("没有注册 : " + eventCode); return; } //一旦注册过这个消息 给所有的脚本 发过去 List<MonoBase> list = dict[eventCode]; for (int i = 0; i < list.Count; i++) { list[i].Execute(eventCode, message); } } private Dictionary<int,List<MonoBase>> dict = new Dictionary<int, List<MonoBase>>(); /// <summary> /// 添加事件 /// </summary> /// <param name="eventCode">Event code.</param> /// <param name="mono">Mono.</param> public void Add(int eventCode, MonoBase mono) { List<MonoBase> list = null; //之前没有注册过 if (!dict.ContainsKey(eventCode)) { list = new List<MonoBase>(); list.Add(mono); dict.Add(eventCode, list); return; } //之前注册过 list = dict[eventCode]; list.Add(mono); } /// <summary> /// 添加多个事件 /// 一个脚本关心多个事件 /// </summary> /// <param name="eventCode">Event code.</param> public void Add(int[] eventCodes, MonoBase mono) { for (int i = 0; i < eventCodes.Length; i++) { Add(eventCodes[i], mono); } } /// <summary> /// 移除事件 /// </summary> /// <param name="eventCode">Event code.</param> /// <param name="mono">Mono.</param> public void Remove(int eventCode, MonoBase mono) { if (!dict.ContainsKey(eventCode)) { Debug.LogWarning("没有这个消息" + eventCode + "注册"); return; } List<MonoBase> list = dict[eventCode]; if (list.Count == 1) dict.Remove(eventCode); else list.Remove(mono); } /// <summary> /// 移除多个 /// </summary> /// <param name="eventCode">Event code.</param> /// <param name="mono">Mono.</param> public void Remove(int[] eventCodes, MonoBase mono) { for (int i = 0; i < eventCodes.Length; i++) { Remove(eventCodes[i], mono); } } }
MsgCenter:
/// <summary> /// 消息处理中心 /// </summary> public class MsgCenter : MonoBase { public static MsgCenter Instance = null; void Awake() { Instance = this; gameObject.AddComponent<AudioManager>(); gameObject.AddComponent<UIManager>(); gameObject.AddComponent<NetManager>(); gameObject.AddComponent<CharacterManager>(); gameObject.AddComponent<SceneMgr>(); DontDestroyOnLoad(gameObject); } /// <summary> /// 参数:区域码、消息、消息参数 /// </summary> /// <param name="areaCode"></param> /// <param name="eventCode"></param> /// <param name="message"></param> public void Dispatch(int areaCode, int eventCode, object message) { switch (areaCode) { case AreaCode.AUDIO: AudioManager.Instance.Execute(eventCode, message); break; case AreaCode.CHARACTER: CharacterManager.Instance.Execute(eventCode, message); break; case AreaCode.NET: NetManager.Instance.Execute(eventCode, message); break; case AreaCode.GAME: break; case AreaCode.UI: UIManager.Instance.Execute(eventCode, message); break; case AreaCode.SCENE: SceneMgr.Instance.Execute(eventCode, message); break; default: break; } } }
AreaCode:
public class AreaCode { /// <summary> /// UI模块 /// </summary> public const int UI = 0; /// <summary> /// GAME模块 /// </summary> public const int GAME = 1; /// <summary> /// CHARACTER模块 /// </summary> public const int CHARACTER = 2; /// <summary> /// NET模块 /// </summary> public const int NET = 3; /// <summary> /// AUDIO模块 /// </summary> public const int AUDIO = 4; /// <summary> /// SCENE模块 /// </summary> public const int SCENE = 5; //.... }
UI模块实现
UIBase:
public class UIBase : MonoBase { /// <summary> /// 自身关心的消息集合 /// </summary> private List<int> list = new List<int>(); /// <summary> /// 绑定一个或多个消息 /// </summary> /// <param name="eventCodes">Event codes.</param> protected void Bind(params int[] eventCodes) { list.AddRange(eventCodes); UIManager.Instance.Add(list.ToArray(), this); } /// <summary> /// 接触绑定的消息 /// </summary> protected void UnBind() { UIManager.Instance.Remove(list.ToArray(), this); list.Clear(); } /// <summary> /// 自动移除绑定的消息 /// </summary> public virtual void OnDestroy() { if (list != null) UnBind(); } /// <summary> /// 发消息 /// </summary> /// <param name="areaCode">Area code.</param> /// <param name="eventCode">Event code.</param> /// <param name="message">Message.</param> public void Dispatch(int areaCode, int eventCode, object message) { MsgCenter.Instance.Dispatch(areaCode, eventCode, message); } }
UIManager:
public class UIManager : ManagerBase { public static UIManager Instance = null; void Awake() { Instance = this; } }
UIEvent:
/// <summary> /// 存储所有的UI消息 /// </summary> public class UIEvent { public const int START_PANEL = 0;//设置开始面板的显示 public const int REGIST_PANE = 1;//设置注册面板的显示 }
PanelA:
public class PanelA : UIBase { private void Awake() { Bind(UIEvent.START_PANEL);//添加对消息的关心 } public override void Execute(int eventCode, object message) { switch (eventCode) //对消息的处理 { case UIEvent.START_PANEL: DoSomeThing(); break; default: break; } } }
其他模块
类似于UI模块,会有一个模块Manager单例类,继承ManagerBase, 另外有一个模块Base类,继承MonoBase,该模块需要实现收发消息功能的脚本都需要继承这个模块Base类,在脚本的Awke函数中添加对某消息的关心,重写父类的Excute函数,当该消息发出时,会执行该Excute函数,对消息做出相应处理
总结
这套基于消息机制的简易框架优点是很明显的,条理清楚,代码组织明确,移植性高,同时,团队开发时交流也方便,当然,也可以对上述框架进行改进,现在是整个项目的消息都由一个字典来维护,可以考虑每个模块维护自己的消息字典(或者其他数据结构);可以根据消息的区域码,如果是本模块的消息,就直接派给本模块,而不经过消息中心,如果消息属于其他模块,就通过消息中心来转发;可以定义一个消息基类MsgBase,所有消息都继承它。使用框架时,需要注意的是,首先要初始化框架层,再执行其他逻辑。每个需要收发消息的脚本,都必须在Awake函数中添加对消息的关心,在OnDestroy方法中移除对消息的关心。