1.2 事件传递流程

请从结构的角度概述,控件的消息模型包含哪些部分,并解释UGUI中点击事件与响应调用是如何关联在一起的。

问题分析

很多Unity 3D项目都使用了UGUI,但并不是所有人都研究过它的内部结构。针对事件的传递过程,会问住大多数未深入思考过的开发者。由于UGUI是开源的,所以我们可以通过查看源码来熟悉它的原理,它的地址在[:uisource]可以找到(前面的标记表示,在我的微信公众号中回复uisource可获得相关内容,主要是担心链接失效)。

事件体系

首先说明,事件体系的基础是设计模式中的观察者模式,因此按照标准的Subject和Observer来解读没有任何问题。但在这里,笔者更希望以功能为导向,将事件体系划分成更易于理解的模块。按这种划分方式,事件体系由四部分组成,分别是:

◎ 监测器(Monito)

◎ 采集器(Collector)

◎ 派发器(Dispatcher)

◎ 响应器(Receiver)

这四个模块大致的依赖关系如图1.5所示。

图1.5

其中用户的操作被监测器驱动的采集器捕获,接着监测器将反馈的信息通知到派发器中,最后通过派发器将事件传播出去。

Unity 3D实现

在Unity 3D中,功能模块的每个部分都有对应的实现类。

◎ 监测器(Monitor)对应的实现类为EventSystem。它重写了MonoBehavior的Update方法,会在每一帧更新挂载在同一个GameObject上的采集器组件状态,并判断是否应该激活派发器。如果是,则调用各个派发器中的派发函数Process。

◎ 采集器(Collector)由两部分组成,对应的实现类分别为BaseInputModule和BaseRaycaster,在UGUI中默认使用的是它们的子类StandaloneInputModule和GraphicRaycaster。当用户操作时,会先由BaseInputModule激活模块,然后发出一个射线点触,返回在BaseRayCaster中能点到的物体并返回信息,交由派发器进行过滤。所以采集器有两端,连接它们要靠EventSystem。整个过程中还有一个静态内部的管理类RaycasterManager,用来做连接采集器和监测器的数据桥梁。

◎ 派发器(Dispatcher)对应的实现类也为BaseInputModule,最常用的是它的子类StandaloneInputMoudle,该类的角色与采集器混在了一起。派发器完成了实际的事件生成,包括且不限于:事件类型的确定、事件内容的提取、派发对象的过滤。其中对派发对象的获取需要借助采集器,但需要通过监测器来驱动。这种设计可以带来效率上的优势,即可以合并采集操作,以达到降低事件频率的目的。

◎ 响应器(Receiver)对应的实现类为IEventSystemHandler及其子类,例如最常用的IPointerClickHandler,它的作用是处理点击事件。通过ExecuteEvents类,可以将发生事件的对象上的所有响应器都获取到并调用其响应逻辑。以点击为例,事件最终会被派发到OnClick的代理上,完成逻辑的执行。

类图

相关实现类有好多,大体分两部分,一部分是功能类,另一部分是编辑器类。

功能类按照前面介绍的事件体系可以清晰地找到重要的基类,它们的UML图如图1.6([:msgclass])所示。

图1.6

这个图只是和点击相关的部分,当然还有很多其他的功能,例如拖曳、滑动等。如果能找到这些重点,其他逻辑就可以结合代码自行推导了。

另一类是编辑器类,这个比较简单,本书没有单独梳理类图,直接用VS自动生成的类图就够用了,如图1.7所示。

图1.7

值得一提的是,UGUI的编辑器界面做得很细致,用起来也很方便。当我们需要自定义Inspector界面时,编辑器代码中的一些功能的实现具有参考价值。

总结

如果还是对点击事件的流程不是很清楚,则可以参笔者绘制的按钮点击消息传递的调用流程,具体如图1.8所示([:msgflow])。

图1.8

扩展问题

(1)请结合设计模式中的观察者模式,分析Unity事件框架的优劣。

(2)如何利用EventSystem来降低事件的频率([:eventrate])?