GraphicRaycaster原理解析

GraphicRaycaster原理解析
GraphicRaycaster图形射线检测器就是专门派驻到每个Canvas画布上的“狙击手”。它的唯一职责就是当玩家点击屏幕时算出当前这个 Canvas 里到底有哪些 UI 元素Image, Text, RawImage 等被点中了。一、 核心属性与配置狙击手的装备在代码的前半部分定义了这个组件在 Inspector 面板中暴露给开发者的配置项ignoreReversedGraphics(忽略反向图形)原理如果一个 UI 图片在 3D 空间中转了 180 度背对着相机你还能点到它吗如果勾选了这个代码底层会通过向量点乘Dot Product计算 UI 朝向与相机视角的夹角如果是背对的直接忽略点击。这在做“UI 翻牌”特效时非常有用。blockingObjects(阻挡遮罩) 与blockingMask原理专为World Space世界空间或Camera Space模式的 Canvas 设计。如果 UI 前面有一堵 3D 的墙或者 2D 的碰撞体射线是否应该被墙挡住如果设为ThreeD或All它会在检测 UI 之前先打一根真正的物理射线去探测有没有撞墙。二、 核心工作流主Raycast方法步骤拆解public override void Raycast(PointerEventData eventData, ListRaycastResult resultAppendList)这是整个脚本的心脏每当玩家点击或拖拽时EventSystem 就会呼叫这个方法。它的执行流程极其严密第一步基础校验与多显示器支持if (canvas null) return; var canvasGraphics GraphicRegistry.GetRaycastableGraphicsForCanvas(canvas); if (canvasGraphics null || canvasGraphics.Count 0) return;先看看画布还在不在。然后通过GraphicRegistry获取这个画布下所有开启了Raycast Target的 UI 元素。接着代码会计算displayIndex。如果你的游戏支持多显示器比如屏幕 1 显示主界面屏幕 2 显示小地图它会丢弃那些不在当前点击显示器上的 UI 事件。第二步视口裁剪 (Viewport Check)pos currentEventCamera.ScreenToViewportPoint(eventPosition); if (pos.x 0f || pos.x 1f || pos.y 0f || pos.y 1f) return;将鼠标屏幕坐标转换为0 到 1的视口坐标。如果鼠标点在了相机视野范围之外直接判定为无效节省后续复杂的数学计算。第三步物理阻挡检测 (Physics Blocking) —— 跨界融合的精髓if (canvas.renderMode ! RenderMode.ScreenSpaceOverlay blockingObjects ! BlockingObjects.None) { // ... 发射 3D/2D 物理射线 ... if (ReflectionMethodsCache.Singleton.raycast3D(ray, out hit...)) hitDistance hit.distance; }如果是 Overlay 模式UI 贴在屏幕最上层物理阻挡无效。如果是其他模式并且开启了阻挡它会调用底层的物理射线。精妙之处它会记录下打中 3D 墙壁的距离存入hitDistance。后续算出来的 UI 距离如果大于这个hitDistance说明 UI 在墙后面点不到第四步纯粹的 2D UI 几何检测 (调用内部私有 Raycast 方法)m_RaycastResults.Clear(); Raycast(canvas, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);这里调用了一个静态内部方法稍后详细讲找出了所有鼠标范围内的 UI 元素。第五步二次验证与数据打包 (打包发货)它遍历上一步找到的所有初步符合条件的 UI 元素进行极其严苛的最终审查反向剔除根据ignoreReversedGraphics再次确认 UI 是否背对相机。计算真实 3D 距离利用射线与平面的数学交点公式distance (Vector3.Dot(transForward, trans.position - ray.origin) / Vector3.Dot(transForward, ray.direction));算出射线碰触到该 UI 的精确距离。物理墙壁剔除if (distance hitDistance) continue;—— 墙在距离 5 的地方UI 在距离 10 的地方UI 被墙挡住剔除生成结果将通过所有测试的 UI 包装成RaycastResult结构体塞入resultAppendList交给EventSystem去做最终排序。三、 内部几何检测私有Raycast方法解析private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IListGraphic foundGraphics, ListGraphic results)在这个循环里它对画布上的每一个开启了RaycastTarget的图像进行4 重过滤状态过滤if (!graphic.raycastTarget || graphic.canvasRenderer.cull || graphic.depth -1)如果目标没开射线检测、或者被系统剔除了、或者还没被渲染过跳过。矩形包围盒过滤 (Rect Transform Check)if (!RectTransformUtility.RectangleContainsScreenPoint(...))这是最高效的数学过滤。计算鼠标坐标是否落在 UI 元素的矩形框RectTransform范围内。如果连矩形框都没进直接跳过。远裁剪面过滤if (eventCamera ! null eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z eventCamera.farClipPlane)如果 UI 跑到了相机视距之外跳过。像素级/高级过滤 (Alpha Hit Test)if (graphic.Raycast(pointerPosition, eventCamera))这一步会调用 UI 元素自身的ICanvasRaycastFilter。比如一张不规则的半透明图片我们设置了 Alpha 阈值点击透明区域无效这部分逻辑就是在这个虚方法里执行的。如果点在了透明处跳过。最后一步局部排序s_SortedGraphics.Sort((g1, g2) g2.depth.CompareTo(g1.depth));把所有选中的 UI按照它们在 Canvas 内部的depth底层 Hierarchy 树的渲染顺序进行降序排列越后画的排越前面然后交付给上层。总结GraphicRaycaster的设计哲学不是真物理而是纯数学它并没有使用 Unity 的 Collider碰撞体来检测点击。对于 UI 来说碰撞体太重了。它全程使用的是RectTransform的几何边界计算包围盒判断和射线平面交点数学公式性能极高。层层漏斗尽早剔除它不会上来就算复杂的 3D 距离。而是先查屏幕区域 - 再查包围盒 - 再查像素 Alpha - 最后查 3D 遮挡。通过一步步的漏斗把大部分无效计算在前期就扼杀掉。完美的解耦它通过GraphicRegistry获取目标而不是自己去GetComponentsInChildren。它把检测结果塞给EventSystem自己绝不越俎代庖去触发点击事件。分工极其明确。