还记得我们的简易消息机制是为了解决什么问题诞生的嘛?

是为了解决脚本间访问的问题。

我们回过头再看下 A 脚本如果想访问 B 脚本,使用消息机制,如何实现。

代码如下:

public class A : MonoBehaviour
{void Update()  {if(Input.GetMouseButtonDown(0)){MsgDispatcher.Send("DO","ok");}}
}public class B : MonoBehaviour
{void Awake(){MsgDispatcher.Register("DO",DoSomething);}void DoSomething(object data){// do something}void OnDestroy(){MsgDispatcher.UnRegiter("DO",DoSomething);}
}

用法还是很简单的。

不过假如我们的 B 脚本注册了非常多的消息,代码会变成如下:

public class B : MonoBehaviour
{void Awake(){MsgDispatcher.Register("DO",DoSomething);MsgDispatcher.Register("DO1",MsgReceiver);MsgDispatcher.Register("DO2",MsgReceiver1);MsgDispatcher.Register("DO3",MsgReceiver2);}void DoSomething(object data){// do something}...void OnDestroy(){MsgDispatcher.UnRegiter("DO",DoSomething);MsgDispatcher.UnRegiter("DO1",MsgReceiver);MsgDispatcher.UnRegiter("DO2",MsgReceiver1);MsgDispatcher.UnRegiter("DO3",MsgReceiver2);}
}

每次注册一个消息,对应地,在 OnDestroy 操作的时候就要注销一个事件。这个非常像我们写 C++ 的时候遵循的一个内存管理法则,每次申请内存就要在析构方法里进行释放。

而这样使用消息机制,初学者非常容易忘记消息的注销,从而导致引用异常等等。

那么如何解决呢?

用一个 Dictionary 记录这个脚本中已经注册过的消息,以及消息名对应的回调。

代码如下:

public class B : MonoBehaviour
{Dictionary<string,Action<object>> mMsgRegisterRecorder = new Dictionary<string,Action<object>>();void Awake(){MsgDispatcher.Register("DO",DoSomething);mMsgRegisterRecorder.Add("DO",DoSomething);MsgDispatcher.Register("DO1",MsgReceiver);mMsgRegisterRecorder.Add("DO1",MsgReceiver);MsgDispatcher.Register("DO2",MsgReceiver1);mMsgRegisterRecorder.Add("DO2",MsgReceiver1);MsgDispatcher.Register("DO3",MsgReceiver2);mMsgRegisterRecorder.Add("DO3",MsgReceiver2);}void DoSomething(object data){// do something}...void OnDestroy(){foreach (var keyValuePair in mMsgRegisterRecorder){MsgDispatcher.UnRegister(keyValuePair.Key,keyValuePair.Value);                }mMsgRegisterRecorder.Clear();}
}

这样,不管注册了多少个消息,只要在 OnDestroy 的时候, 进行一个遍历,这样消息就全部注销掉了。

但是这样写的话注册,就变得麻烦了,每次注册要先两行代码。

MsgDispatcher.Register("DO3",MsgReceiver2);
mMsgRegisterRecorder.Add("DO3",MsgReceiver2);

把两行提取成一个方法就好了。
提取的方法,代码如下:

private void RegisterMsg(string msgName, Action<object> onMsgReceived)
{MsgDispatcher.Register(msgName, onMsgReceived);mMsgRegisterRecorder.Add(msgName, onMsgReceived);
}

而注册消息的代码就会变成如下:

private void Awake()
{RegisterMsg("Do",DoSomething);RegisterMsg("DO1",MsgReceiver);RegisterMsg("DO2", _=>{ });RegisterMsg("DO3", _=>{ });
}

是不是精简了很多,而且也可以注册 Lambda 表达式了。

不过我们看下现在的 B 脚本全部代码:

    public class B : MonoBehaviour{Dictionary<string, Action<object>> mMsgRegisterRecorder = new Dictionary<string, Action<object>>();private void Awake(){RegisterMsg("Do",DoSomething);RegisterMsg("DO1",_=>{ });RegisterMsg("DO2", _=>{ });RegisterMsg("DO3", _=>{ });}private void RegisterMsg(string msgName, Action<object> onMsgReceived){MsgDispatcher.Register(msgName, onMsgReceived);mMsgRegisterRecorder.Add(msgName, onMsgReceived);}void DoSomething(object data){// do something}private void OnDestroy(){foreach (var keyValuePair in mMsgRegisterRecorder){MsgDispatcher.UnRegister(keyValuePair.Key,keyValuePair.Value);                }mMsgRegisterRecorder.Clear();}}

目前,每个要使用相同消息策略的脚本,都实现如上的代码,会产生很多的重复代码。所以这里我们要开始考虑如何让这个消息注册/注销的策略进行复用。首先用静态方法是不可能了,因为这个策略是有状态的(成员变量)。所以以我们目前掌握的知识来看,只能用继承的方式了。

继承也有两种,一种是继承一个新类,另一种是继承到 MonoBehaviourSimplify 里。

笔者选择后者,这样我们的脚本只要继承 MonoBehaviourSimplify 就会获得 API 简化和消息功能了,一举多得,而且很方便。

集成后的代码,也就是第十三个示例的代码如下:

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(){}}
}

在以上代码里,笔者把 MonoBehaviourSimplify 添加了 abstract 关键字,这样用户在使用 MonoBehaviourSimplify 的时候就不能自己创建出来实例了。

而又添加了如下抽象方法:

protected abstract void OnBeforeDestroy();

做这步的目的呢,是为了提醒子类不要覆写了 OnDestroy。提醒是怎么做到的呢。

我们通过分析可以得出,使用 MonoBehaviourSimplify 的情况有两种。
一种是,在写脚本之前就想好了这个脚本要继承 MonoBehaviourSimplify,但是继承之后,编译会报错,因为有一个抽象方法,必须实现,也就是 OnBeforeDestroy。那么实现了这个,用户就会知道设计 MonoBehaviourSimplify 的人,是推荐用 OnBeforeDestroy 来做卸载逻辑的,并不推荐用 OnDestroy。这是第一种。

第二种呢,脚本本来就有了,但是在中途想要换成继承 MonoBehaviourSimplify,继承了之后,同样报错了,报错了之后发现 MonoBehaviourSimplify 推荐用 OnBeforeDestroy 来做卸载逻辑,这时候如果以前的脚本已经有了 OnDestroy 逻辑,用户就会把 OnDestroy 的逻辑迁移到 OnBeforeDestroy 里。这样也算达到了一个提醒的作用。

这就是 OnBeforeDestroy 的设计初衷,而 abstract 关键字,就应该这样用。

但是到这里呢,这套策略还是有一点小问题的。这个小问题就留在下一篇讲了。

今天的内容就这些,我们下一篇再见。

转载请注明地址:凉鞋的笔记:liangxiegame.com

更多内容

  • QFramework 地址:https://github.com/liangxiegame/QFramework

  • QQ 交流群:623597263

  • Unity 进阶小班

    • 主要训练内容:

      • 框架搭建训练(第一年)
      • 跟着案例学 Shader(第一年)
      • 副业的孵化(第二年、第三年)
    • 权益、授课形式等具体详情请查看《小班产品手册》:https://liangxiegame.com/master/intro
  • 关注公众号:liangxiegame 获取第一时间更新通知及更多的免费内容。

Unity 游戏框架搭建 2019 (四十七) 集成到 MonoBehaviourSimplify相关推荐

  1. Unity 游戏框架搭建 2019 (四十五) 独立的方法和独立的类

    我们在开始本示例之前,先整理出我们当前库中的代码类型. 工具方法:CommonUtil.GameObjectSimplify等. 类: MonoBehaviourSimplify. 静态方法中的方法全 ...

  2. Unity 游戏框架搭建 2019 (四十二) MonoBehaviour 简化

    在前两篇,我们完成了第九个示例.为了完善第九个示例,我们复习了类的继承,又学习了泛型和 params 关键字. 我们已经接触了类的继承了.接触继承之前,把类仅仅当做是方法的集合,接触了继承之后,我们的 ...

  3. Unity 游戏框架搭建 2018 (四) 我所理解的框架

    前言 架构和框架这些概念听起来很遥远,让很多初学者不明觉厉.会产生"等自己技术牛逼了再去做架构或者搭建框架"这样的想法.在这里笔者可以很肯定地告诉大家,初学者是完全可以去做这些事情 ...

  4. Unity 游戏框架搭建 (七) 减少加班利器-QApp类

    本来这周想介绍一些框架中自认为比较好用的小工具的,但是发现很多小工具都依赖一个类----App. App类的职责: 1.接收Unity的生命周期事件. 2.做为游戏的入口. 3.一些框架级别的组件初始 ...

  5. Unity 游戏框架搭建 (九) 减少加班利器-QConsole

    为毛要实现这个工具? 在我小时候,每当游戏在真机运行时,我们看到的日志是这样的. 没高亮啊,还有乱七八糟的堆栈信息,好干扰日志查看,好影响心情. 还有就是必须始终连着usb线啊,我想要想躺着测试... ...

  6. Unity 游戏框架搭建 (二十一) 使用对象池时的一些细节

    上篇文章使用SafeObjectPool实现了一个简单的Msg类.代码如下: class Msg : IPoolAble,IPoolType{#region IPoolAble 实现public vo ...

  7. Unity 游戏框架搭建 (五) 简易消息机制

    什么是消息机制? 23333333,让我先笑一会. 为什么用消息机制? 三个字,解!!!!耦!!!!合!!!!. 我的框架中的消息机制用例: 1.接收者 using UnityEngine;names ...

  8. class unity 定义类_Unity 游戏框架搭建 2019 (二十五) 类的第一个作用 与 Obselete 属性...

    在上一篇我们整理到了第七个示例,我们今天再接着往下整理.我们来看第八个示例: #if UNITY_EDITORusing UnityEditor; #endif using UnityEngine; ...

  9. Unity 游戏框架搭建 2018 (一) 架构、框架与 QFramework 简介

    约定 还记得上版本的第二十四篇的约定嘛?现在出来履行啦~ 为什么要重制? 之前写的专栏都是按照心情写的,在最初的时候笔者什么都不懂,而且文章的发布是按照很随性的一个顺序.结果就是说,大家都看完了,都还 ...

最新文章

  1. Flutter 配置
  2. 基于opencv的简单视频处理类示例
  3. Hive自定义UDF的JAR包加入运行环境的方法
  4. dojo Quick Start/dojo入门手册--开始使用dojo.js
  5. 大数据WEB阶段(三)CSS
  6. 推箱子java下载_Java实现简单推箱子游戏
  7. php mysql数据库 指南_用 PHP 创建 MySQL 数据库
  8. PHP中上传文件文件名乱码
  9. android StringBuffer实现换行
  10. 前端学习(570):margin负值下的等高布局
  11. yeoman、bower、grunt 开发收集
  12. fseek函数与ftell函数使用例程
  13. MEGA-X 3D打印机教程:01_我的第一台3D打印机参数
  14. BUUCTF:[XMAN2018排位赛]file
  15. 读书笔记: 与爱因斯坦月球漫步-记忆术
  16. Dart语言中的Isolate
  17. 《苏菲的世界》读书笔记之一:自然派哲学家
  18. 读到哪句你心痛了??
  19. 开水果店前期投入,水果店会有哪些费用
  20. 提笔,再回忆~落笔,成悔,一切皆已随风:伤感日志

热门文章

  1. Marathon和Kubernetes的区别
  2. 访问学者J1签证通常准备那些材料?
  3. codeforces+contest+985D. Sand Fortress+思维
  4. C#系统实战(2):把登录模块化,用login.login(self)调用
  5. linux日志记录分析,linux日志朔源分析记录
  6. c语言如何给位域变量赋值,C语言下位操作(使用位域方法)
  7. IRIS 2021 技术文档 First Look 15 -- 使用 XEP 实例化存储 Java 对象
  8. 家庭网络WIFI相关知识
  9. 洛谷:ISBN号码(c语言)
  10. 遗传算法详细介绍以及基于遗传算法和非线性规划函数的寻优算法—MATLAB实现