QFramework 是一套 渐进式 的 快速开发 框架。目标是作为无框架经验的公司、独立开发者、以及 Unity3D 初学者们的 第一套框架。框架内部积累了多个项目的在各个技术方向的解决方案。学习成本低,接入成本低,重构成本低,二次开发成本低,文档内容丰富(提供使用方式以及原理、开发文档)。github:https://github.com/liangxiegame/QFramework
本来在介绍消息机制之前中间还有篇有限状态机的文章介绍,不过我再游戏设计模式分类中已经写了一篇,个人觉得还是很好理解的
地址在这:https://blog.csdn.net/dengshunhao/article/details/80808152
或者 QFramework作者也写了一篇:http://liangxiegame.com/post/4/
同时,本篇文章介绍内容地址:http://liangxiegame.com/post/5/
在此之前,首先你需要知道C# This扩展知识,已经给你总结好啦 :
https://blog.csdn.net/dengshunhao/article/details/80944994
咳咳,看完基础开始正文
1.什么是消息机制?
使用消息机制的目的是解耦合
2.框架中所使用的消息机制
涉及到三个类,分别是:Sender,Receiver,MsgDispatcher
public class Sender : MonoBehaviour, IMsgSender
{
void Update()
{
this.SendLogicMsg("Receiver Show Sth", "发送:你好", "发送:世界");
}
}
public class Receiver : MonoBehaviour, IMsgReceiver
{
// Use this for initialization
private void Awake()
{
this.RegisterLogicMsg("Receiver Show Sth", ReceiveMsg);
}
private void ReceiveMsg(object[] args)
{
foreach (var arg in args)
{
Log.I("接收:"+arg);
}
}
}
看代码我们可以看到,sender里面不停的发送信息 发送:你好 发送:世界 两条消息,接收端则在Awake中注册了接收方法,细心的应该注意到了两者传的第一个参数是相同的,再来看看效果:
接收端一直在输出接收到的信息
再回到代码里,发现sender receiver里面并没有啥实现方法,那就只能是继承的接口里面咯,看到这如果没看C# this扩展的童鞋估计会疑惑啦,借口不是只能声明方法吗?子类没实现怎么没报错呢?我们来仔细看看接口所在的类的代码
首先是两个接口啦
public interface IMsgReceiver
{
}
public interface IMsgSender
{
}
再是消息分发器MsgDispatcher中消息捕捉器的定义,包括receiver对应的回调方法
/// <summary>
/// 消息捕捉器
/// </summary>
private class LogicMsgHandler
{
public readonly IMsgReceiver Receiver;
public readonly Action<object[]> Callback;
public LogicMsgHandler(IMsgReceiver receiver, Action<object[]> callback)
{ Receiver = receiver;
Callback = callback;
}
}
mMsgHandlerDict是消息名称对应的消息捕捉器,当receiver注册接收消息的时候会添加
/// <summary>
/// 每个消息名字维护一组消息捕捉器。
/// </summary>
static readonly Dictionary<string, List<LogicMsgHandler>> mMsgHandlerDict =
new Dictionary<string, List<LogicMsgHandler>>();
IMsgReceiver扩展方法(就是往mMsgHandlerDict中添加消息名对应的消息捕捉器):
/// <summary>
/// 注册消息,
/// 注意第一个参数,使用了C# this的扩展,
/// 所以只有实现IMsgReceiver的对象才能调用此方法
/// </summary>
public static void RegisterLogicMsg(this IMsgReceiver self, string msgName, Action<object[]> callback)
{
if (string.IsNullOrEmpty(msgName))
{
Log.W("RegisterMsg:" + msgName + " is Null or Empty");
return;
}
if (null == callback)
{
Log.W("RegisterMsg:" + msgName + " callback is Null");
return;
}
if (!mMsgHandlerDict.ContainsKey(msgName))
{
mMsgHandlerDict[msgName] = new List<LogicMsgHandler>();
}
var handlers = mMsgHandlerDict[msgName];
// 防止重复注册
foreach (var handler in handlers)
{
if (handler.Receiver == self && handler.Callback == callback)
{
Log.W("RegisterMsg:" + msgName + " ayready Register");
return;
}
}
handlers.Add(new LogicMsgHandler(self, callback));
}
Receiver = receiver;
Callback = callback;
}
}
IMsgSender扩展(其实是调用已经注册的消息名对应的回调方法):
/// <summary>
/// 发送消息
/// 注意第一个参数 表示是IMsgSender的扩展
/// </summary>
public static void SendLogicMsg(this IMsgSender sender, string msgName, params object[] paramList)
{
if (string.IsNullOrEmpty(msgName))
{
Log.E("SendMsg is Null or Empty");
return;
}
if (!mMsgHandlerDict.ContainsKey(msgName))
{
Log.W("SendMsg is UnRegister");
return;
}
var handlers = mMsgHandlerDict[msgName];
var handlerCount = handlers.Count;
// 之所以是从后向前遍历,是因为 从前向后遍历删除后索引值会不断变化
// 参考文章,http://www.2cto.com/kf/201312/266723.html
for (var index = handlerCount - 1; index >= 0; index--)
{
var handler = handlers[index];
if (handler.Receiver != null)
{
Log.W("SendLogicMsg:" + msgName + " Succeed");
handler.Callback(paramList);
}
else
{
handlers.Remove(handler);
}
}
}
IMsgReceiver注销方法扩展(其实就是移除mMsgHandlerDict中的对应消息):
/// <summary>
/// 注销消息
/// 注意第一个参数,使用了C# this的扩展,
/// 所以只有实现IMsgReceiver的对象才能调用此方法
/// </summary>
public static void UnRegisterLogicMsg(this IMsgReceiver self, string msgName, Action<object[]> callback)
{
if (msgName.IsNullOrEmpty() || null == callback)
{
return;
}
var handlers = mMsgHandlerDict[msgName];
// 删除List需要从后向前遍历
for (var index = handlers.Count - 1; index >= 0; index--)
{
var handler = handlers[index];
if (handler.Receiver == self && handler.Callback == callback)
{
handlers.Remove(handler);
break;
}
}
}
- 如果是MonoBehaviour注册消息之后,GameObject Destroy之前一定要注销消息,之前的解决方案是,自定义一个基类来维护该对象已经注册的消息列表,然后在基类的OnDestory时候遍历卸载。