UGUI鼠标点击到按钮响应流程的源码分析

主要是求职复习中记的笔记,估计不太好看懂,各位可以参考一下。本篇是翻UGUI源码总结的,可以对照源码自己看看。

资料

(99+ 封私信 / 84 条消息) Unity入门精要 - 知乎

UGUI事件原理简述

本篇文章详细研究从玩家点击到按钮响应的流程,即事件处理流程。首先根据鼠标位置找到最上方的图形(继承自graphic,如Image等,以下称图形)。然后分发一些事件,分别向父物体查找第一个能接受的控件(继承自Selectable,如button等,以下称控件),让这个控件响应点击。

这个流程优点在于,可以在多个按钮重叠的时候,只响应最上方按钮。同时事件是分别处理的,可以实现能点击的同时也响应父物体的拖动。缺点在于点击穿透难以处理。

具体过程如下:

因为总体流程很复杂,我们以函数为单位,说明其所有逻辑。调用的子函数会写在下个段落。

EventSystem.Update总入口

调用所有InputModule(我们下面只说明了StandaloneInputModule.Process)执行输入模块的tick

执行GetMousePointerEventData,根据屏幕坐标执行++EventSystem.RaycastAll++(见下段落),查询能点到的物体

查询第一个结果就是玩家点到的东西。点击结果将保存在PointerInputModule.m_PointerData。复制几份,分别给左右中键使用

调用ExecuteEvents.ExecuteHierarchy查找点击物体上的selectable控件

如果没有找到,调用ExecuteEvents.GetEventHandler在父物体上查找

最终调用ExecuteEvents.Execute,执行事件回调。

EventSystem.RaycastAll

找到所有BaseRaycast(一般就一个GraphicRaycaster),分别执行++Raycast++(见下一个段落),将结果汇总。

对结果使用EventSystem.RaycastComparer进行排序。优先级分别为

RaycastResult.module.eventCamera.depth(降序)

RaycastResult.module.sortOrderPriority

RaycastResult.module.renderOrderPriority

RaycastResult.sortingLayer

RaycastResult.sortingOrder

RaycastResult.depth(仅canvas相同)

RaycastResult.distance

RaycastResult.sortingGroupID

RaycastResult.sortingGroupOrder

RaycastResult.index

GraphicRaycaster.Raycast

GraphicRaycaster.Raycast访问Canvas的GraphicRegistry来获取所有可射线检测图形。(GraphicRegistry.m_Graphics缓存所有图形,GraphicRegistry.m_RaycastableGraphics缓存所有可射线检测图形)

多屏幕支持,检查触发点击的屏幕是否为当前相机的屏幕

读取鼠标位置,使用相机将其转换为视口坐标系,超出视口的点击剔除。如果没有相机直接使用屏幕尺寸转换

根据鼠标位置和相机进行射线检测,找到最近的模型的距离,用来在下面进行模型遮挡剔除

(需要GraphicRaycaster的RenderMode不是ScreenSpaceOverlay,并且blockingObjects不是None)

调用另一个++GraphicRaycaster.Raycast++函数进行射线检测(见下方,参数不同的重载)

对所有检测到的图形进行处理:

如果打开GraphicRaycaster.ignoreReversedGraphics,旋转到背面的图形不会被点击。(仅看方向,scale为负无影响)

检查图形是否在镜头后面。镜头后的物体被排除。

与之前计算出的最近的模型的距离比较,如果模型更近,图形不会被点击

结果放入resultAppendList中返回

GraphicRaycaster.Raycast

忽略raycastTarget为false图形,忽略canvasRenderer.cull没打开图形,忽略depth==-1图形

调用RectTransformUtility.RectangleContainsScreenPoint检查点击点是否在图形的矩形内。如果修改raycast padding则矩形范围会变化。

将图形转换到镜头坐标系,检查其z轴是否超出远平面farClipPlane

调用++Graphic.Raycast++检查。此函数可重载。

检查完毕,使用图形的depth排序。结果在results中传出

Graphic.Raycast

检查isActiveAndEnabled

从本节点开始,依次向父物体查找:

如果父物体有Canvas,且overrideSorting(什么东西?)被打开,不再继续查找

查找父物体的ICanvasRaycastFilter。

如果是CanvasGroup,处理ignoreParentGroups逻辑。他会让CanvasGroup的所有父物体不再响应事件。

调用ICanvasRaycastFilter.IsRaycastLocationValid检查。遮罩等会使用这个来屏蔽所有子物体被隐藏的区域。

源码中IsRaycastLocationValid的使用情况

CanvasGroup:直接返回blocksRaycasts

Mask和RectMask2D:点击点需要在mask范围内才能响应

Image:与图片进行透明度测试。需要设置alphaHitTestMinimumThreshold

如果点击失败了,有那些可能?

graphic的raycast target没打开,图形禁用或物体隐藏

graphic的raycast padding如果被设置,会影响响应范围。

有更靠前的graphic挡住了点击

没有控件接受响应,控件禁用或物体隐藏,有其他控件先拦截了事件。

实现了ICanvasRaycastFilter方法,用代码修改了响应范围,这个接口可控制其所有子节点的响应

CanvasGroup的ignoreParentGroups打开后,之后的父物体不再影响点击范围

Image有个自带的根据透明度进行不规则形状点击功能alphaHitTestMinimumThreshold。

CanvasGroup的blocksRaycasts关闭时下面所有图形都不响应

被mask或者rectMask2D隐藏的位置不会响应点击

需要有EventSystem,Canvas上需要有GraphicRaycaster

打开GraphicRaycaster的blockingObjects时,3DUI可能会与模型遮挡,导致点不到UI

打开GraphicRaycaster的blocking mask时,检测不到没有在mask中物体

打开GraphicRaycaster.ignoreReversedGraphics时,方向为反向的图形不会被响应

镜头后方的UI不会被响应,镜头外的UI不会被响应,z轴异常可能导致不响应

注意事项

EventSystem中有个currentSelectedGameObject,标记了当前选中的物体。一般鼠标移入的物体被自动选中,可以使用键盘方向键切换,手柄摇杆可以切换。请使用这个切换选中,可以方便的支持多输入设备,如同时使用手机触屏和游戏手柄控制。

EventSystem.IsPointerOverGameObject可以用来检查鼠标是否停留在UI上,挺常用。

EventSystem.RaycastAll可以用来模拟UI点击过程,获取点击列表。能用来做点击穿透等。

调用ExecuteEvents.GetEventHandler查找点击某个物体时,会接受事件控件。会向父物体查找。

调用ExecuteEvents.Execute,可执行事件回调,与底层实现相同。

要拓展自己的控件,只要实现对应接口即可拦截事件,支持的事件见下表。官方有个EventTrigger,不过应该没人用吧?

将raycast padding调到无穷大,然后实现ICanvasRaycastFilter接口,将框检测反转,就能实现拦截不在框里的点击。这个应该能用来新手引导(禁用其他位置的点击)

PhysicsRaycaster等能实现模型的点击。

scroll下面放按钮等,就能实现可点击同时也能拖拽。

由于支持了移入选中等逻辑,事件检测是每帧都做的,并且计算量很大,对性能有较大影响。

所有支持的事件event整理

|----------|---------------------------------|
| 基类 | IEventSystemHandler |
| 指针进入 | IPointerEnterHandler |
| 指针离开 | IPointerExitHandler |
| 按下按钮 | IPointerDownHandler |
| 松开按钮 | IPointerUpHandler |
| 点击按钮 | IPointerClickHandler |
| 初始化拖拽 | IInitializePotentialDragHandler |
| 开始拖拽 | IBeginDragHandler |
| 正在拖拽 | IDragHandler |
| 结束拖拽 | IEndDragHandler |
| 拖拽目标 | IDropHandler |
| 滚轮滚动 | IScrollHandler |
| 选中对象tick | IUpdateSelectedHandler |
| 被选中 | ISelectHandler |
| 被取消选中 | IDeselectHandler |
| 移动 | IMoveHandler |
| 确定 | ISubmitHandler |
| 取消 | ICancelHandler |

优化方法

减少打开raycast的graphic。一个控件尽量只使用一个带raycast的图形。注意背景一般需要无响应raycast图形来避免点击穿透。

解决方案

默认关闭raycast。

建立静态检查,扫描出所有具有多个图形的控件,并提示修改。

提供工具快速关闭不需要raycast。

提供工具预览控件响应范围(也能用来查按钮响应范围异常)。

maskable的优化可以一起做了。关闭不使用mask图形的开关,能降低消耗。

之前写的raycast笔记,参考这篇里射线检测过程部分

相关推荐
熊猫悟道16 小时前
Unity shader 之,Shader内部时间离散处理
unity·游戏引擎·材质·着色器
PA_17 小时前
unity Component-Based Architecture游戏框架
游戏·unity·游戏引擎
yi碗汤园20 小时前
C#实现对UI元素的拖拽
开发语言·ui·unity·c#
jtymyxmz21 小时前
《Unity Shader》11.3.2 广告牌技术
unity·游戏引擎
jtymyxmz1 天前
《Unity Shader》11.3.1 流动的河流
unity·游戏引擎
jtymyxmz1 天前
《Unity Shader》11.3.1 续 流动的水流的阴影
unity·游戏引擎
世洋Blog1 天前
Unity性能优化-2d游戏的DrawCall
游戏·unity·面试·性能优化·游戏引擎
jtymyxmz1 天前
《Unity Shader》11.2.2 滚动的背景
unity·游戏引擎
Tatalaluola1 天前
Unity使用EPPlus读取写入表格
unity·c#·游戏引擎·excel