UGUI学习 - 事件系统、射线检测
图片转自大佬博客,非常好的入门:https://zhuanlan.zhihu.com/p/437704772。
事件系统
总的来说事件系统分为3个大组件:
- EventSystem组件 负责管理 所有的输入检测模块(InputModule)并在Update中每帧调用Module的执行(Process)。
- InputModule组件 负责输入(点击、拖拽、选中等),调用Raycaster获得返回值PointerEventData,最后通过ExecuteEvents触发事件(IPointerClickHandler 等)。
- Raycaster组件 负责确定目标对象,返回结果PointerEventData给InputModule组件。
相互之间的协作⭐
EventSystem
会在Update
中调用输入模块的Process
方法来处理输入消息;
PointerInputModule
的Process
方法会调用EventSystem
中的RaycastAll
方法进行射线检测;
RaycastAll
又会调用所有BastRaycaster
的Raycast
方法执行具体的射线检测操作,用以获取屏幕某个点下的所有目标;
获取完点击目标后,又会回到PointerInputModule
,对其触发那些事件接口(IPointerClickHandler之类的)并传入PointerEventData参数 => 使用冒泡排序通知,直到有能处理对应IEventSystemHandler
的UI接收为止(比如Button上的Text无点击事件,那就父物体Button接收)。
协作举例
举个例子来体验上面的协作。
比如你点击了一个Button组件,首先会在EventSystem
的Update
中被输入模块的Process
方法被抓取到,之后输入模块就会调用RaycastAll
方法来得到所有屏幕下点击到的目标,最后通过冒泡的方式来找到第一个可以接收点击事件的UI目标。确定好UI目标后,对其执行点击事件。
管理者 EventSystem
平时新建一个Canvas时,会创建EventSystem组件,这个组件所挂载的脚本就是EventSystem和InputModule。
EventSystem组件主要负责处理输入、射线投射以及发送事件,一个场景中只能有一个EventSystem组件。
- 管理哪个游戏对象被认为是选中的
- 管理正在使用的输入模块
- 管理射线检测(如果需要)
- 根据需要更新所有输入模块
输入 InputModule
平时新建一个Canvas时,会创建EventSystem组件,这个组件所挂载的脚本就是EventSystem和InputModule。
BaseInputModule基类,负责发送输入事件(点击、拖拽、选中等)到具体对象,可以自己派生实现输入模块而官方提供2个:
- 标准输入模块(StandaloneInputModule)
- 触摸输入模块(TouchInputModule, 现在已经不需要了它整合进StandaloneInputModule中了!)
这2个官方提供的模块会检测一些输入操作,以事件的方式(message系统)通知目标对象,所以给摇杆的mono脚本实现接口(IPointerClickHandler 等)就可以触发事件 。
射线 Raycaster
- 对于UI对象,在平时新建一个Canvas时,如果Canvas的渲染模式是SceenSpace-Overlay,Canvas会挂载GraphRaycaster脚本。
- 场景中的非UI对象,如果想要接收输入模块的事件,也需要给摄像机挂上一个射线检测组件(3D是PhysicsRaycaster、2D是Physics2Draycaster)。然后需要给想被检测到的物体添加collider脚本,否则检测不到。
事件执行器 ExecuteEvents
事件执行器,InputModule在通过射线确定了最近的命中物体后,会用ExecuteEvents触发Click等事件。
Execute方法:获取物体上所有包含IEventSystemHandler且可用的组件,根据情况调用执行接口方法,传入PointerEventData参数。
射线检测流程
0.在EventSystem的Update中,每帧调用InputModule.Process,进行点击/触击检测。
1.如果InputModule检测到了点击/触击,就会向EventSystem请求发射射线。 eventSystem.RaycastAll(pointerData, m_RaycastResultCache);
2.将屏幕触击点投影到相机切面(near)上的位置,生成一条射线,将射线所有命中的物体保存到 hitDistance击中点数组 和 RaycastResult被命中物体数组 中。具体射线怎么计算命中的,是UnityEngine.Physics
内的接口,没开源。
3.遍历所有Graphic,获取可接收射线广播的Graphic信息。
4.取出RaycastResult数组中,距离最近的那个物体作为射线检测的结果返回。
5.射线检测结果PointerEventData 返回到了InputModule中,对其触发那些事件接口(IPointerClickHandler之类的)并传入PointerEventData参数。
// 2中具体还会记录射线起点到相机渲染终点的切面(far)之间的距离,如果射线触点不在范围内会判定为不触发点击事件。这个具体看下图。
UI穿透
就是说,在UGUI和3D场景混合的情况下,点击UI区域同时也会触发3D中物体的⿏标事件。
解决1.一般新窗口和底层窗口之间,都会有一个暗灰色的透明背景,可以把这个透明背景的Raycast Target勾选上,这样就终止了射线检测到后面的窗口。
解决2.自己实现一个简单的射线检测,射线使用graphicRaycaster射线,这是针对UI检测的射线。
ps.针对UI检测的射线:其实每个组件在创建的时候已经被添加进了一个公共列表(我们使用的UGUI中的每个组件都是继承自Graphic或者依赖一个继承自Graphic的组件)。