UGUI事件系统分析
UGUI事件系统分析
一句话概括
InputModule在EventSystem的驱动下不断生成事件,利用Raycaster查找到能响应事件的GameObject后调用其相应的事件处理接口。
EventSystem
模块驱动
它是事件系统暴露给外面的管理器,可以查询当前选中物体、设置当前选中物体等。
在内部,它驱动输入模块。它的主要特点:
- 它持有并维护同
GameObject上的所有BaseInputModule。 - 它在
Update方法中调用所有BaseInputModule的UpdateModule方法。 - 然后抉择出当前活动的
BaseInputModule,原则是BaseInputModule列表中第一个IsModuleSupported为true,并且ShouldActivateModule为true的模块。 - 对转为非活动态的
BaseInputModule调用DeactivateModule方法,对转为活动态的BaseInputModule调用ActivateModule方法 - 调用当前活动的
BaseInputModule的Process方法。
RaycastAll方法
此工具方法是提供给BaseInputModule用的,它输入一个PointerEventData,输出光线投射的结果列表。
它从RaycasterManager里依次获取活动状态的BaseRaycaster,调用它的Raycast方法。最后对结果列表进行排序。
排序原则:
- 如果两个结果由不同的
Raycaster产生: 如果两个Raycaster都有eventCamera,eventCamera的depth较大的排在前面;否则Raycaster的sortOrderPriority较大的排在前面;再次Raycaster的renderOrderPriority较大的排在前面。 - 如果两个结果由同一个
Raycaster产生: 结果的sortingLayer的id较大者排前面;否则如果两者的根Raycaster相同,结果的depth较大者排前面;再次结果的distance较小者排前面;再次结果的index较小值排前面。
ToString
ToString方法的值会显示在检视面板,它会调用活动BaseInputModule的ToString,因此自己实现模块时最好重写下ToString返回一些有用信息。
BaseRaycaster
该类组件负责对场景里的对象进行光线投射,找出光标处的对象。
在OnEnable方法中,它将自己加入到RaycasterManager的列表中;在OnDisable方法中,它将自己从RaycasterManager中称除。
PhysicsRaycaster
需要Camera组件,它是用Camera的ScreenPointToRay方法生成Ray用Physics类的Raycast系列方法去获取所有命中对象,并按从近到远排序。注意它产生的结果的sortingLayer和sortingOrder都设为0。
GraphicRaycaster
需要Canvas组件以获取其sortingOrder和renderOrder。
它有两个属性值得关注: BlockingObjects 和 BlockingMask 。这两者用来设置哪些物体可能阻挡对UI元素的光线投射,BlockingObjects.None表示无遮挡,BlockingObjects.ThreeD表示3D物理对象会遮挡,BlockingObjects.TwoD表示2D物理对象会遮挡,BlockingObjects.All表示两者皆会。而BlockingMask则是指定遮挡对象的层级。
当进行遮挡检查时,会得到一个最近遮挡对象的hitDistance。这一步就是用Physics类的Raycast系列方法完成。
当光线投射时,它从GraphicRegistry中获取属于Canvas的所有Graphic对象,对于每个Graphic对象:如果它可接受投射、Rect范围包含传入点、有Camera时在Camera的Clip区域,则调用该Graphic的Raycast方法并在返回true情况下加入结果列表。然后按Graphic的depth从大到小排序。
然后对于上一步拿到的元素列表,依次检查其与Camera的distanc是否满足大于0且小于hitDistance(如无Camera或Canvas的renderMode为RenderMode.ScreenSpaceOverlay,distance直接为0)。满足的元素进入最终的结果列表。
Graphic的depth
Graphic的depath来源于其同对象上的CanvasRenderer的absoluteDepth,指该渲染器相对于根画布的深度,由引擎内部计算。可能是对象的层级和兄弟节点关系的计算值。
拿到Raycat的结果列表后,在最终的使用时默认是使用结果列表中的第一个。
BaseInputModule
产生事件并发送给GameObject
StandaloneInputModule
默认的子类就是StandaloneInputModule说下它的Process流程:
发送一个空白事件给当前选中对象上的IUpdateSelectedHandler接口。
处理触摸事件。如果无触摸事件且有鼠标,再处理鼠标事件。
如果启用了导航事件,处理导航移动事件、导航发送事件。
事件如何产生
以鼠标事件为例:
将鼠标位置和与上一帧差异更新到鼠标状态对象的按键对象中,并且调用EventSytem.RaycastAll取得第一个对象作为事件对象。
将左、右、中键的按下\抬起状态更新到鼠标状态对象的按键对象中
处理左键的Press、Move、Drag事件。
处理右键和中键的Press、Drag事件。
处理滚轮事件。
通过判断按键对象的各种状态(每个鼠标按键会维护一个状态对象),可以判断出当前是否产生了点击、移入、拖动等事件。
如何找到真正能响应事件的对象
ExecuteEvents.GetEventHandler<T>方法,它传入的是Rayast获得的GameObject,返回的是真正有实现了T接口的组件的GameObject。
此方法从传入的GameObject向其Transform层级的根方向进行冒泡,直到找到一个有实现了T接口组件的对象。
如何查找实现了T接口的组件
调用GameObject的GetComponents方法,然后对每个方法用is T判断,并看其是否处于启用状态。
事件如何发送到对象相关的接口
都是用的ExecuteEvents.Execute<T>方法,它接受一个对象、一个事件、一个T接口转发函数。
注意,T是接口类型而不是事件类型,事件都是以BaseEventData类型传入,在T接口转发函数中才校验其是否是实际需要的类型。
在内部,它收集对象上所有实现了T接口的组件,然后用转发函数对每一个组件调用其接口方法。
对于不同的接口T,它的方法名当然不一样,因此事件系统为每一种事件处理接口提供了一个转发函数。因此ExecuteEvents.Execute<T>方法就不需要知道每种接口的函数名是什么,它只管调用传入的转发函数去完成对接口的调用。
里面对于各种事件生成的状态管理和焦点对象变更的通知管理比较细节,这里不细说了。

浙公网安备 33010602011771号