图片转自大佬博客,非常好的入门:https://zhuanlan.zhihu.com/p/437704772。

事件系统

总的来说事件系统分为3个大组件:

  • EventSystem组件 负责管理 所有的输入检测模块(InputModule)并在Update中每帧调用Module的执行(Process)。
  • InputModule组件 负责输入(点击、拖拽、选中等),调用Raycaster获得返回值PointerEventData,最后通过ExecuteEvents触发事件(IPointerClickHandler 等)。
  • Raycaster组件 负责确定目标对象,返回结果PointerEventData给InputModule组件。

相互之间的协作⭐

EventSystem会在Update中调用输入模块的Process方法来处理输入消息;

PointerInputModuleProcess方法会调用EventSystem中的RaycastAll方法进行射线检测;

RaycastAll又会调用所有BastRaycasterRaycast方法执行具体的射线检测操作,用以获取屏幕某个点下的所有目标;

获取完点击目标后,又会回到PointerInputModule,对其触发那些事件接口(IPointerClickHandler之类的)并传入PointerEventData参数 => 使用冒泡排序通知,直到有能处理对应IEventSystemHandler的UI接收为止(比如Button上的Text无点击事件,那就父物体Button接收)。

协作举例

举个例子来体验上面的协作。

比如你点击了一个Button组件,首先会在EventSystemUpdate中被输入模块的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的组件)。