Unity Messaging System
Messaging System 是 Unity 全新设计的一套通信系统。旨在解决一些使用 SendMessage
可能会出现的问题。最重要的是 Messaging System 设计之初就是为游戏开发过程提供一套完整的通信解决方案,所以它不仅仅局限于在 UI 系统中使用,在游戏代码中的任何地方我们都可以使用它。
接下来就来看看为什么要使用这套 Messaging System。
传统方式进行 Message 通信
传统消息通信,通常我们会使用 GameObject 类中的 SendMessage
、SendMessageUpwards
和 BroadcastMessage
方法,每个方法用途不一样:
SendMessage(string methodName, object value, SendMessageOptions options);
调用当前 GameObject 中所有 MonoBehaviour 脚本中的 methodName 方法。
SendMessageUpwards(string methodName, object value);
调用当前以及所有祖先 GameObject 对象中的所有 MonoBehaviour 脚本中的 methodName 方法。
BroadcastMessage(string methodName, object parameter);
调用当前以及所有子孙 GameObject 对象中的所有 MonoBehaviour 脚本中的 methodName 方法。
上面是传统方式进行通信常用的三个方法,总结一下:
SendMessage | SendMessageUpwards | BroadcastMessage | |
---|---|---|---|
自身节点 | √ | √ | √ |
兄弟节点 | × | × | × |
父/祖先节点 | × | √ | × |
子/孙节点 | × | × | √ |
接下来看看传统方式进行 Message 通信可能遇到的一些问题。
首先,在使用以上三种方式通信时,都将需要回调的方法名以字符串的形式传入作为第一个参数。以 SendMessage
方法为例,首先会寻找当前 SendMessage
方法所在的 GameObject 中所有的 Component;然后在使用第一个参数 methodName,遍历比较每个 Component 中所有的方法,如果方法名相同,则执行该 Component 中的 methodName 方法。SendMessageUpwards
和 BroadcastMessage
也大同小异,不同之处在于这两个方法还要分别首先找到多个接收执行该方法的 GameObject(祖先对象或子孙对象),然后在对每个对象执行类似 SendMessage
的步骤。
看到这里,你可能会想这样如果需要接收消息的 GameObject 很多,并且方法也很多,那么可能会有性能问题。为什么都得到了 GameObject 的所有的 Component,缓存这些 Component 以后直接执行消息对应的方法不就可以了吗?确实可以,但是这样的代码好像就不太优雅了,破坏了 Unity 原有的 Component/Object 模式。
另一个可怕问题,如果消息发送者没有管理好消息的出口,那么某些可能你不希望的接收者却被激活触发了未知的逻辑。
最后还有一点,传统方式进行 Message 通信虽然代码结构上看很简洁,但是对刚接触项目代码新人来说,要想找到一个消息发送来源以及所有可能得接收者却稍显麻烦。
综合以上几点可能会出现的问题,下面就一起来看看能解决这些问题的 Messaging System。
Messaging System
The new UI system uses a messaging system designed to replace SendMessage. The system is pure C# and aims to address some of the issues present with SendMessage.
上面是 Unity 官方文档对 Messaging System 的介绍。在 UGUI 的 Event System 中,所有的事件通信都是用了 Messaging System 来实现,它也解决了前面我们所说的传统方式进行 Message 通信中可能会遇到的一些问题。下面就来让我们好好看看这套 Messaging System。
使用 ExecuteEvents 通信
首先,要想让 Component 能够从 Messaging System 接收消息,Component 要实现 IEventSystemHandler
这个接口。
接着就可以使用以下几个方法发送事件。
Execute(GameObject target, EventSystems.BaseEventData eventData, EventFunction<T> functor);
执行 GameObject 上挂载的所有实现了 IEventSystemHandler
接口的脚本中的特定事件。
ExecuteHierarchy(GameObject root, EventSystems.BaseEventData eventData, EventFunction<T> callbackFunction);
以 GameObject 为根节点开始向父节点递归的执行 Execute
方法,直到找到一个可以处理 IEventSystemHandler
事件的 GameObject 为止。
Messaging System 内部以 C# 代理为基础,实现了整个通信系统。下面就来分析以下整个 ExecuteEvents 类的源码部分。
在 ExecuteEvents 类内部定义了一个代理
EventFunction<T1>(T1 handler, BaseEventData eventData)
,这个代理就是用来执行自定义消息响应操作。它有两个参数: 第一个是泛型的一个 handler,第二个是 BaseEventData 类型的事件数据。然后来到最关键
Execute
方法。方法定义如下:
|
|
该方法需要三个参数: 第一个参数 target 是当前接收事件的 GameObject 对象;第二个参数 eventData 是携带的 BaseEventData 类型事件数据;最后一个参数 functor 是 EventFunction
Execute
方法首先使用 GetEventList<T>(GameObject go, IList<IEventSystemHandler> results)
获取对象 target
上实现了自定义 IEventSystemHandler
接口并处于 Active 和 Enable 的 Component;然后循环将每个 Component 和 eventData
作为参数执行一遍代理方法 functor
(代理的方法中具体代码可以自己实现,一般情况下是回调 Component 中的相关事件方法);最后返回结果是一个 bool 值表示是否有合适处理消息事件的 Component 被找寻到,有则返回 true
。
下面是处理 UI 事件按下的一个方法,其内部实现就是回调了 IPointerDownHandler
的 OnPointerDown
方法。这种处理 UI 事件的方法在 ExecuteEvents 类内部定义了很多,后面会有介绍。
|
|
- 接下来看看
ExecuteHierarchy
方法。定义如下:
|
|
该方法同 Execute
方法类似,同样需要三个参数。前面提到过 ExecuteHierarchy
方法是以某个节点为起点向祖先节点递归,直到找寻到某个对象上的 Component 能够处理 IEventSystemHandler
类型的消息事件就停止,并返回这个 GameObject。所以,这个方法中的第一个参数就是起始节点 GameObject。
ExecuteHierarchy
方法的代码也很简单,首先调用 GetEventChain(GameObject root, IList<Transform> eventChain)
方法得到 root
节点(包括本身)所有的祖先节点,然后循环对每个节点 GameObject 调用 Execute
方法,如果节点 GameObject 上有合适处理消息事件的 Component,则返回这个节点 GameObject 并结束循环。
GetEventHandler
方法。定义如下:
|
|
GetEventHandler
方法作用是从指定的节点 GameObject 开始向上寻找,直至找到一个能够接收指定类型事件的节点 GameObject 就返回这个 GameObject,否则返回 null。
GetEventHandler
内部实现也很简单。遍历当前节点以及祖先节点,获取每个节点 GameObject 上的所有 Components,然后在判断每个 Component 是否能接收指定类型事件并且是否处于激活状态,如果找到的符合条件的 Component 数量大于 0,则说明当前节点 GameObject 能够接收指定类型事件,返回当前 GameObject;否则继续向上寻找。
内置 UI 事件
ExecuteEvents 类内部定义了很多通用的 EventFunction<T1>(T1 handler, BaseEventData eventData)
类型的代理方法,用来执行 Event System 指定的事件。比如:
|
|
这些代理方法在 Event System 内部会用到,我们也可以实现类似 IPointerClickHandler
这些接口去监听某些事件。比如 Image 组件本身不能监听鼠标点击事件,现在我们需要为 Image 组件添加点击点击响应,实现就很简单了: 给 Image 组件添加一个实现了 IPointerClickHandler
接口的脚本,当这个 Image 上有点击事件时,Event System 会将找到这个 Image 然后执行 Execute
方法(传入的第一个参数就是 Image 所在的对象,第二个参数是 PointerEventData,最后的参数就是 ExecuteEvents 类成员 s_PointerClickHandler
),接下来的步骤就回到了之前分析的 Execute
执行过程,最终回调了我们自己实现的 OnPointerClick
方法。
Unity 中提供的常用的 UI 事件能让开发变得更方便,更多具体事件接口说明参考文档。