Unity 全局控制 UI 点击事件(拓展:全局控制其它涉及到 EventSystem 的事件(输入、射线投射和发送事件))
全局控制 UI 点击事件
- Unity 点击事件都是基于
IPointerClickHandler
接口,凡是实现了该接口的对象都会触发点击事件
1 | public interface IPointerClickHandler : IEventSystemHandler |
- 继续查在哪调用的,从
EventSystem
里面找到了BaseInputModule
1 | private BaseInputModule m_CurrentInputModule; |
- 它就是跟
EventSystem
挂载一起的StandaloneInputModule
继承的基类 - 然后看
StandaloneInputModule
里面的代码,全局查找IPointerClickHandler
1 | private void ReleaseMouse(PointerEventData pointerEvent, GameObject currentOverGo) |
- 大概意思就是在释放鼠标的时候会检查一次点击事件,核心代码就一句
1 | ExecuteEvents.Execute(pointerEvent.pointerClick, pointerEvent, ExecuteEvents.pointerClickHandler); |
Execute
内部代码
1 | public static bool Execute<T>(GameObject target, BaseEventData eventData, EventFunction<T> functor) where T : IEventSystemHandler |
- 也就是说最后会执行
functor(handler, eventData)
,它是从前面传过来的,也就是ExecuteEvents.pointerClickHandler
,继续看它的内部实现
1 | public static EventFunction<IPointerClickHandler> pointerClickHandler => s_PointerClickHandler; |
- 是一个委托,指向了
s_PointerClickHandler
1 | private static readonly EventFunction<IPointerClickHandler> s_PointerClickHandler = Execute; |
- 至此就明了了,所有的点击事件都是通过 invoke 这个
s_PointerClickHandler
委托实现的。那我们就重写覆盖这个委托,因为是private
的,所以需要用到反射。核心代码如下
1 | System.Type type = typeof(ExecuteEvents); |
拓展
- 继续看
ExecuteEvents
静态类的内部,可以发现所有的输入事件调用都在这里执行
1 | public delegate void EventFunction<T1>(T1 handler, BaseEventData eventData); |
- 同理,通过反射获取相应委托并进行重写即可达到控制相应事件的效果
Unity UGUI 事件的底层原理
- 在 Unity 场景中创建一个
Canvas
,可以发现编辑器自动创建了一个叫EventSystem
的东西,可以发现这个EventSystem
中默认包含两个组件:EventSystem
和StandaloneInputModule
。这便是 UGUI 事件系统。 - 在游戏运行过程中一个 UI 元素捕获用户的输入和操作从而驱动应用程序作出反应,这其中涉及到 UI 的显示,事件调度、UI 捕获、事件处理。
事件调度
- 事件调度发生在
EventSystem
的Update
中,找到当前的Module
并处理,处理中包含了对移动点击拖动事件的处理。首先来看下事件数据有哪几种。
事件数据
- BaseEventData:是事件数据类的父类,其中包括
EventSystem
、InputModule
和当前选中GameObject
的引用。 - AxisEventData:滚轮事件数据,只记录滚动的方向数据。
- PointerEventData:点位事件数据,其中包含当前位置,滑动距离,点击时间以及不同状态下 GameObject 的引用。
- 当点击事件发生时,UGUI 可以获得点位事件数据,这是后续处理该事件重要的依据,在整个事件处理流程中进行传递。
事件类型
- 输入检测模块规定了对事件的处理逻辑和细节,如处理鼠标点击事件,拖拽和移动等,其中
TouchInputModule
主要是面向触摸平台和移动设备的输入检测模块,StandaloneInputModule
主要是面向标准鼠标键盘的。 - 除了 Unity 提供的
StandaloneInputModule
和TouchInputModule
之外,我们也可以通过泛化BaseInputModule
来自定义InputModule
。处理的过程其实就是重写父类的Process
函数,在其内部对鼠标光标的各种状态进行计算和标记。
UI 捕获
- 在获取一个点位事件数据时,会计算当前点位事件对应的 GameObject 。
- UI 捕获通过射线检测,从摄像机出发出射线穿过当前
Pointer
或者Touch
所在位置,获得碰撞的 GameObject 列表。 - 射线碰撞检测模块的主要工作是从摄像机的屏幕位置上进行射线碰撞检测并获取碰撞结果,将结果返回给事件处理逻辑类,交由事件处理模块处理。UI 是分层的,在顶层的当然是先被照射的,当然免不了有些 UI 控件是忽略射线检测的。
- 射线检测在 Unity 中分为
PhysicsRaycaster
,Physics2DRaycaster
以及GraphicRaycaster
。2D 射线碰撞检测、3D 射线碰撞检测相对比较简单,采用射线的形式进行碰撞检测。 - 区别在于 2D 射线碰撞检测结果里预留了 2D 的层级次序,以便在后面的碰撞结果排序时,以这个层级次序为依据进行排序,而 3D 射线碰撞检测结果则是以距离大小为依据进行排序的。
GraphicRaycaster
类为 UGUI 元素点位检测的类,它被放在Core
渲染块里。它主要针对ScreenSpaceOverlay
模式下的输入点位进行碰撞检测,因为这个模式下的检测并不依赖于射线碰撞,而是通过遍历所有可点击的 UGUI 元素来进行检测比较,从而判断该响应哪个 UI 元素的。因此GraphicRaycaster
类是比较特殊的。
事件处理
- 对于发生的事件,会由
ExecuteEvents
中的Execute
方法找到合适的方法处理。 - 现在我们知道 Unity 里通过
InputModule
把用户的输入处理完之后包装到BaseEventData
里,而且也通过射线检测捕获到了对应的 UI ,那么我们就可以调用该 GameObject 所实现的任意一个接口的接口函数。 - 例如鼠标光标事件有很多种,比如:
光标按下-Down
、抬起-Up
、离开-Exit
,如果按下和抬起的时间很短又可以产生点击事件-Click
,对于每一种事件都可以定义为一个接口,例如:IPointerDownHandler
、IPointerUpHandler
、IPointerEnterHandler
、IPointerExitHandler
、IPointerClickHandler
,为了统一操作这些接口,它们都继承自IEventSystemHandler
。 - 不同的 UI 组件可以选择性的支持这些鼠标事件,比如有的控件我们就是不希望它响应点击事件,那么不让它实现
IPointerClickHandler
的接口就可以。 - 既然 Button 实现了
IPointerClickHandler
接口,那么对于一个按钮控件来说,当光标点击事件到来时,就可以调用到它 Button 组件的OnPointerClick
函数。 - UGUI 把封装好的数据传递给 UI 组件,那么这些组件在调用其对应类型的接口函数时,便会触发相应事件。对于
IPointerClickHandler
按钮来说,鼠标点击一个按钮时,便会调用它的OnPointerClick
接口,在OnPointerClick
中触发onClick
事件。接下来便是监听onClick
去编写业务逻辑了。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 SleepyLoser's Blog!
评论