Unity 游戏框架搭建 2019 (四十八/四十九) MonoBehaviourSimplify 中的消息策略完善&关于发送事件的简单封装
MonoBehaviourSimplify 中的消息策略完善
在上一篇,笔者说,MonoBehaviourSimplify 中的消息策略还有一些小问题。我们在这篇试着解决一下。
先贴出来代码:
using System;
using System.Collections.Generic;
namespace QFramework
{
public abstract partial class MonoBehaviourSimplify
{
Dictionary<string, Action<object>> mMsgRegisterRecorder = new Dictionary<string, Action<object>>();
protected void RegisterMsg(string msgName, Action<object> onMsgReceived)
{
MsgDispatcher.Register(msgName, onMsgReceived);
mMsgRegisterRecorder.Add(msgName, onMsgReceived);
}
private void OnDestroy()
{
OnBeforeDestroy();
foreach (var keyValuePair in mMsgRegisterRecorder)
{
MsgDispatcher.UnRegister(keyValuePair.Key,keyValuePair.Value);
}
mMsgRegisterRecorder.Clear();
}
protected abstract void OnBeforeDestroy();
}
public class B : MonoBehaviourSimplify
{
private void Awake()
{
RegisterMsg("Do", DoSomething);
RegisterMsg("DO1", _ => { });
RegisterMsg("DO2", _ => { });
RegisterMsg("DO3", _ => { });
}
void DoSomething(object data)
{
// do something
}
protected override void OnBeforeDestroy()
{
}
}
}
我们是使用字典进行注册消息的记录的,使用字典就要保证字典中的 key 是唯一的。而我们很可能在一个脚本中对一个关键字注册多次,这样用字典这个数据结构就显得不合理了。
相比字典,List 更合适,因为我们有有可能有重复的内容,而字典更适合做一些查询工作,但是 List 并不支持键值对,怎么办呢?
我们只好创建一个结构来存储我们的消息名和对应的委托,这个结构是一个类叫做 MsgRecord
消息策略部分的代码如下:
public abstract partial class MonoBehaviourSimplify
{
List<MsgRecord> mMsgRecorder = new List<MsgRecord>();
private class MsgRecord
{
public string Name;
public Action<object> OnMsgReceived;
}
protected void RegisterMsg(string msgName, Action<object> onMsgReceived)
{
MsgDispatcher.Register(msgName, onMsgReceived);
mMsgRecorder.Add(new MsgRecord
{
Name = msgName,
OnMsgReceived = onMsgReceived
});
}
private void OnDestroy()
{
OnBeforeDestroy();
foreach (var msgRecord in mMsgRecorder)
{
MsgDispatcher.UnRegister(msgRecord.Name,msgRecord.OnMsgReceived);
}
mMsgRecorder.Clear();
}
protected abstract void OnBeforeDestroy();
}
代码比较简单。
而我们的示例代码,如下,增加了一行重复注册的代码。
public class B : MonoBehaviourSimplify
{
private void Awake()
{
RegisterMsg("Do", DoSomething);
RegisterMsg("Do", DoSomething);
RegisterMsg("DO1", _ => { });
RegisterMsg("DO2", _ => { });
RegisterMsg("DO3", _ => { });
}
void DoSomething(object data)
{
// do something
}
protected override void OnBeforeDestroy()
{
}
}
而我们的 MonoBehaviourSimplify 内部实现发生了天翻地覆的变化,也没有对我们的示例代码产生一点影响,这叫封装。
那么到这里,我们的消息策略还有问题吗?
还有的,问题在创建 MsgRecord 的部分。
如下:
mMsgRecorder.Add(new MsgRecord
{
Name = msgName,
OnMsgReceived = onMsgReceived
});
我们每次注册消息,都要 new 一个 MsgRecord 对象出来,而我们在注销的时候,对这个对象是什么都没有做的,注销的代码如下:
foreach (var msgRecord in mMsgRecorder)
{
MsgDispatcher.UnRegister(msgRecord.Name,msgRecord.OnMsgReceived);
}
这样会造成一个性能问题,这个性能问题主要是有 new 时候寻址造成的,具体原因自行搜索,当然在本专栏的后边还是会介绍的。我们要做的,就是减少 new 的发生次数,要想减少,就得让我们的 MsgRecord 能够回收利用。
如何回收利用呢,答案是维护一个容器,比如 List 或者 Queue、Stack 等,也就是传说中的对象池。由于我们的 MsgRecord 的作用仅仅是作为一个存储结构而已,而存储的顺序也不是很重要,所以我们就用做简单的 Stack 结构,也就是栈,来作为 MsgRecord 对象池的容器。
其实现如下:
private class MsgRecord
{
static Stack<MsgRecord> mMsgRecordPool = new Stack<MsgRecord>();
public static MsgRecord Allocate()
{
if (mMsgRecordPool.Count > 0)
{
return mMsgRecordPool.Pop();
}
return new MsgRecord();
}
public void Recycle()
{
Name = null;
OnMsgReceived = null;
mMsgRecordPool.Push(this);
}
public string Name;
public Action<object> OnMsgReceived;
}
由于这个对象池只给 MsgRecord 用,所以就在 MsgRecord 内部实现了。
Allocate 是申请,也就是获取对象。Recycle 就是回收,当不用的时候调用一下就好了。
原理很简单。而 mMsgRecordPool 之所以设置成了 private 访问权限,是因为,不希望被外部访问到。对于一个类的设计来讲,MsgRecord 是一个非常合格的类了。
应用到我们的消息策略的代码如下:
protected void RegisterMsg(string msgName, Action<object> onMsgReceived)
{
MsgDispatcher.Register(msgName, onMsgReceived);
//
var msgRecord = MsgRecord.Allocate();
msgRecord.Name = msgName;
msgRecord.OnMsgReceived = onMsgReceived;
mMsgRecorder.Add(msgRecord);
}
private void OnDestroy()
{
OnBeforeDestroy();
foreach (var msgRecord in mMsgRecorder)
{
MsgDispatcher.UnRegister(msgRecord.Name,msgRecord.OnMsgReceived);
//
msgRecord.Recycle();
}
mMsgRecorder.Clear();
}
我们发现,在申请对象部分可以简化成如下:
// var msgRecord = MsgRecord.Allocate();
//
// msgRecord.Name = msgName;
// msgRecord.OnMsgReceived = onMsgReceived;
//
// mMsgRecorder.Add(msgRecord);
mMsgRecorder.Add(MsgRecord.Allocate(msgName, onMsgReceived));
只需要向 MsgRecord.Allocate 增加参数,代码如下:
public static MsgRecord Allocate(string msgName,Action<object> onMsgReceived)
{
MsgRecord retMsgRecord = null;
retMsgRecord = mMsgRecordPool.Count > 0 ? mMsgRecordPool.Pop() : new MsgRecord();
retMsgRecord.Name = msgName;
retMsgRecord.OnMsgReceived = onMsgReceived;
return retMsgRecord;
}
代码不难,那么到这里,我们的完整的第十三个示例就写完了。
完整示例代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace QFramework
{
public abstract partial class MonoBehaviourSimplify
{
List<MsgRecord> mMsgRecorder = new List<MsgRecord>();
private class MsgRecord
{
private static readonly Stack<MsgRecord> mMsgRecordPool = new Stack<MsgRecord>();
public static MsgRecord Allocate(string msgName,Action<object> onMsgReceived)
{
MsgRecord retMsgRecord = null;
retMsgRecord = mMsgRecordPool.Count > 0 ? mMsgRecordPool.Pop() : new MsgRecord();
retMsgRecord.Name = msgName;
retMsgRecord.OnMsgReceived = onMsgReceived;
return retMsgRecord;
}
public void Recycle()
{
Name = null;
OnMsgReceived = null;
mMsgRecordPool.Push(this);
}
public string Name;
public Action<object> OnMsgReceived;
}
protected void RegisterMsg(string msgName, Action<object> onMsgReceived)
{
MsgDispatcher.Register(msgName, onMsgReceived);
mMsgRecorder.Add(MsgRecord.Allocate(msgName, onMsgReceived));
}
private void OnDestroy()
{
OnBeforeDestroy();
foreach (var msgRecord in mMsgRecorder)
{
MsgDispatcher.UnRegister(msgRecord.Name,msgRecord.OnMsgReceived);
msgRecord.Recycle();
}
mMsgRecorder.Clear();
}
protected abstract void OnBeforeDestroy();
}
public class MsgDistapcherInMonoBehaviourSimplify : MonoBehaviourSimplify
{
#if UNITY_EDITOR
[UnityEditor.MenuItem("QFramework/13.消息机制集成到 MonoBehaviourSimplify", false, 14)]
private static void MenuClicked()
{
UnityEditor.EditorApplication.isPlaying = true;
new GameObject("MsgReceiverObj")
.AddComponent<MsgDistapcherInMonoBehaviourSimplify>();
}
#endif
private void Awake()
{
RegisterMsg("Do", DoSomething);
RegisterMsg("Do", DoSomething);
RegisterMsg("DO1", _ => { });
RegisterMsg("DO2", _ => { });
RegisterMsg("DO3", _ => { });
}
private IEnumerator Start()
{
MsgDispatcher.Send("Do","hello");
yield return new WaitForSeconds(1.0f);
MsgDispatcher.Send("Do","hello1");
}
void DoSomething(object data)
{
// do something
Debug.LogFormat("Received Do msg:{0}",data);
}
protected override void OnBeforeDestroy()
{
}
}
}
运行结果如下图:

菜单栏如下图:

目录如下图:

到这里我们可以进行一次导出了。
关于发送事件的简单封装
在上一篇,我们在 MonoBehaviourSimplify 中集成了消息功能。而在做消息功能的过程中,又接触了对象池实现了一个非常简单版本。
今天呢我们在接着学习。
我们先回顾下 MonoBehaviourSimplify 中关于消息功能的使用方法。
注册消息,直接用 RegisterMsg,而注销则在 OnDestroy 的时候统一进行注销。
那么单独注销时候怎么办呢?这是第一个问题。
第二个问题是,发送消息,我们使用的是 MsgDispatcher.Send 这个方法。
和我们的注册消息的方法不是统一的。这是第二个问题。
第一个问题
第一个问题解决很简单,只要增加针对一个消息注销的方法就好了。
代码如下:
public partial class MonoBehaviourSimplify
{
protected void UnRegisterMsg(string msgName)
{
var selectedRecords = mMsgRecorder.FindAll(recorder => recorder.Name == msgName);
selectedRecords.ForEach(selectRecord =>
{
MsgDispatcher.UnRegister(selectRecord.Name, selectRecord.OnMsgReceived);
mMsgRecorder.Remove(selectRecord);
selectRecord.Recycle();
});
selectedRecords.Clear();
}
protected void UnRegisterMsg(string msgName, Action<object> onMsgReceived)
{
var selectedRecords = mMsgRecorder.FindAll(recorder => recorder.Name == msgName && recorder.OnMsgReceived == onMsgReceived);
selectedRecords.ForEach(selectRecord =>
{
MsgDispatcher.UnRegister(selectRecord.Name, selectRecord.OnMsgReceived);
mMsgRecorder.Remove(selectRecord);
selectRecord.Recycle();
});
selectedRecords.Clear();
}
}
FindAll 是一个查询方法,在 mMsgRecorder 内查询出所有符合条件的项。代码没有太大的难度。
不过在使用上要注意一下,如果是要重复注册并且需要注销的消息,最好是用成员方法来接收,而不是用委托接收,原因是如果是单独注销这类消息的时候,最好是用上边代码的第二种注销方法,用第一种的话,可能把当前脚本之前注册的同名消息都会注销掉。不过这是极少数的情况,一般笔者些项目根本用不到单独注销,而是全部交给了 OnDestroy 处理。
这样第一个问题算是解决了
接下来是我们第二个问题。
第二个问题:
第二个问题是 API 不统一的问题。这个问题要解决起来很简单。只要实现一个 Send 方法就好了,而 Send 中主要逻辑有 MsgDispatcher.Send 完成。
代码如下:
protected void SendMsg(string msgName, object data)
{
MsgDispatcher.Send(msgName, data);
}
到此呢,我们的 API 就统一了。而第十四个示例也就算 OK 了。
全部代码如下:
using System;
using UnityEngine;
namespace QFramework
{
public partial class MonoBehaviourSimplify
{
protected void UnRegisterMsg(string msgName)
{
var selectedRecords = mMsgRecorder.FindAll(recorder => recorder.Name == msgName);
selectedRecords.ForEach(selectRecord =>
{
MsgDispatcher.UnRegister(selectRecord.Name, selectRecord.OnMsgReceived);
mMsgRecorder.Remove(selectRecord);
});
selectedRecords.Clear();
}
protected void UnRegisterMsg(string msgName, Action<object> onMsgReceived)
{
var selectedRecords = mMsgRecorder.FindAll(recorder =>
recorder.Name == msgName && recorder.OnMsgReceived == onMsgReceived);
selectedRecords.ForEach(selectRecord =>
{
MsgDispatcher.UnRegister(selectRecord.Name, selectRecord.OnMsgReceived);
mMsgRecorder.Remove(selectRecord);
});
selectedRecords.Clear();
}
protected void SendMsg(string msgName, object data)
{
MsgDispatcher.Send(msgName, data);
}
}
public class UnifyAPIStyle : MonoBehaviourSimplify
{
#if UNITY_EDITOR
[UnityEditor.MenuItem("QFramework/14.统一 API 风格", false, 14)]
private static void MenuClicked()
{
UnityEditor.EditorApplication.isPlaying = true;
new GameObject("MsgReceiverObj")
.AddComponent<UnifyAPIStyle>();
}
#endif
private void Awake()
{
RegisterMsg("OK", data =>
{
Debug.Log(data);
UnRegisterMsg("OK");
});
}
private void Start()
{
SendMsg("OK","hello");
SendMsg("OK","hello");
}
protected override void OnBeforeDestroy()
{
}
}
}
示例代码很简单,执行的结果如下图所示:

菜单栏如下图:

目录如下图:

这样我们的第十四个示例就完成了,可以进行一次导出了。
今天的内容就这些,我们下一篇再见,拜拜~
转载请注明地址:凉鞋的笔记:liangxiegame.com
更多内容
QFramework 地址:https://github.com/liangxiegame/QFramework
QQ 交流群:623597263
Unity 进阶小班:
- 主要训练内容:
- 框架搭建训练(第一年)
- 跟着案例学 Shader(第一年)
- 副业的孵化(第二年、第三年)
- 权益、授课形式等具体详情请查看《小班产品手册》:https://liangxiegame.com/master/intro
- 主要训练内容:
关注公众号:liangxiegame 获取第一时间更新通知及更多的免费内容。

Unity 游戏框架搭建 2019 (四十八/四十九) MonoBehaviourSimplify 中的消息策略完善&关于发送事件的简单封装的更多相关文章
- Unity 游戏框架搭建 2019 (二十七、二十八)弃用的代码警告解决&弃用的代码删除
在前两篇,我们把所有的示例重头到尾整理了一遍. 当前的状态如下: 要做的事情: (完成) 备份:导出文件,并取一个合理的名字. 遗留问题: (完成) 第八个示例与之前的示例代码重复,功能重复. (完成 ...
- Unity 游戏框架搭建 2019 (二十一、二十二) 第三章简介&整理前的准备
整理前的准备 到目前为止,我们积攒了很多示例了,并且每个示例也都贯彻了最的约定和规则. 在上一篇的小结也说了一个比较新的东西:编程体验优化. 在之前我们还积攒了一个问题:代码重复问题. 我们可是忍住整 ...
- # Unity 游戏框架搭建 2019 (三十四、三十五) 9 ~ 10 示例整理
第九个示例 目前代码如下: using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace QFramework { p ...
- Unity 游戏框架搭建 2019 (九~十二) 第一章小结&第二章简介&第八个示例
第一章小结 为了强化教程的重点,会在合适的时候进行总结与快速复习. 第二章 简介 在第一章我们做了知识库的准备,从而让我们更高效地收集示例. 在第二章,我们就用准备好的导出工具试着收集几个示例,这些示 ...
- Unity 游戏框架搭建 2019 (十八~二十) 概率函数 & GameObject 显示、隐藏简化 & 第二章 小结与快速复习
在笔者刚做项目的时候,遇到了一个需求.第一个项目是一个跑酷游戏,而跑酷游戏是需要一条一条跑道拼接成的.每个跑道的长度是固定的,而怪物的出现位置也是在跑道上固定好的.那么怪物出现的概率决定一部分关卡的难 ...
- Unity 游戏框架搭建 2019 (三十六~三十八) partial与public
在上一篇,我们把菜单的顺序从头到尾整理了一遍.在整理菜单顺序的过程中,记录了一个要做的事情. 要做的事情: (完成) 备份:导出文件,并取一个合理的名字. 整理完菜单顺序后,学习新的知识,解决随着示例 ...
- Unity 游戏框架搭建 2019 (三十九、四十一) 第四章 简介&方法的结构重复问题&泛型:结构复用利器
第四章 简介 方法的结构重复问题 我们在上一篇正式整理完毕,从这一篇开始,我们要再次进入学习收集示例阶段了. 那么我们学什么呢?当然是学习设计工具,也就是在上篇中提到的关键知识点.这些关键知识点,大部 ...
- Unity 游戏框架搭建 2019 (四十六) 简易消息机制 & 集成到 MonoBehaviourSimplify 里
在上一篇,我们接触了单例,使用单例解决了我们脚本之间访问的问题. 脚本之间访问其实有更好的方式. 我们先分下脚本访问脚本的几种形式. 第一种,A GameObject 是 B GameObject 的 ...
- Unity 游戏框架搭建 2019 (五十二~五十四) 什么是库?&第四章总结&第五章简介
在上一篇,我们对框架和架构进行了一点探讨.我们在这一篇再接着探讨. 什么是库呢? 来自同一位大神的解释: 库, 插到 既有 架构 中, 补充 特定 功能. 很形象,库就是搞这个的.我们的库最初存在的目 ...
随机推荐
- Servlet 教程——检视阅读
Servlet 教程--检视阅读 参考 Servlet教程--菜鸟--蓝本 Servlet教程--w3cschool Servlet教程--易百 servlet依赖maven依赖: <!--se ...
- Vxlan L2
VXLAN(Virtual eXtensible LAN可扩展虚拟局域网)诞生了,基于IP网络之上,采用的是MAC in UDP技术 跨三层实现二层通信 总结为何需要Vxlan: 虚拟机规模受到网络规 ...
- [UWP]推荐一款很Fluent Design的bilibili UWP客户端 : 哔哩
UWP已经有好几个Bilibili的客户端,最近有多了一个: 哔哩 - Microsoft Store 作者云之幻是一位很擅长设计的UWP开发者,我也从他那里学到了很多设计方面的技巧.它还是一位Bil ...
- 经典卷积神经网络算法(1):LeNet-5
.caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px so ...
- springboot设置过滤器、监听器、拦截器
其实这篇文章算不上是springboot的东西,我们在spring普通项目中也是可以直接使用的 设置过滤器: 以前在普通项目中我们要在web.xml中进行filter的配置,但是只从servlet 3 ...
- ssm(spring,spring mvc,mybatis)框架
ssm框架各个技术的职责 spring :spring是一个IOC DI AOP的 容器类框架 spring mvc:spring mvc 是一个mvc框架 mybatis:是一个orm的持久层框架 ...
- Spark离线日志分析,连接Spark出现报错
首先,我的代码是这样的 import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} object ...
- Azure AD(二)调用受Microsoft 标识平台保护的 ASP.NET Core Web API 上
一,引言 上一节讲到Azure AD的一些基础概念,以及Azure AD究竟可以用来做什么?本节就接着讲如何在我们的项目中集成Azure AD 包含我们的API资源(其实这里还可以在 SPA单页面应用 ...
- redis crackit入侵事件总结
今天发现服务器有异常进程/opt/yam/yam,上网搜了搜,是由于redis未授权引起的入侵,查了些资料,这里做下总结. 1. 现象 有以下其一现象就要注意是否被入侵 crontab -l 可以看到 ...
- CentOS7 Installing Python3
最近开始学习python. python火了这么久,我终于还是跪舔它了,我是一个跟风的人,学过C.C#.JAVA.PHP,无一例外的浅尝即止,不知道我这双已经近视的眼,确认过的眼神还对不对,希望pyt ...