GraphicRaycaster原理解析
2026/7/23 4:30:45 网站建设 项目流程

GraphicRaycaster(图形射线检测器)就是专门派驻到每个Canvas(画布)上的“狙击手”。

它的唯一职责就是:当玩家点击屏幕时,算出当前这个 Canvas 里,到底有哪些 UI 元素(Image, Text, RawImage 等)被点中了。

一、 核心属性与配置(狙击手的装备)

在代码的前半部分,定义了这个组件在 Inspector 面板中暴露给开发者的配置项:

  1. ignoreReversedGraphics(忽略反向图形)

    • 原理:如果一个 UI 图片在 3D 空间中转了 180 度(背对着相机),你还能点到它吗?如果勾选了这个,代码底层会通过向量点乘(Dot Product)计算 UI 朝向与相机视角的夹角,如果是背对的,直接忽略点击。这在做“UI 翻牌”特效时非常有用。

  2. blockingObjects(阻挡遮罩) 与blockingMask

    • 原理:专为World Space(世界空间)或Camera Space模式的 Canvas 设计。如果 UI 前面有一堵 3D 的墙,或者 2D 的碰撞体,射线是否应该被墙挡住?如果设为ThreeDAll,它会在检测 UI 之前,先打一根真正的物理射线去探测有没有撞墙。

二、 核心工作流:主Raycast方法步骤拆解

public override void Raycast(PointerEventData eventData, List<RaycastResult> 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 元素,进行极其严苛的最终审查:

  1. 反向剔除:根据ignoreReversedGraphics再次确认 UI 是否背对相机。

  2. 计算真实 3D 距离:利用射线与平面的数学交点公式distance = (Vector3.Dot(transForward, trans.position - ray.origin) / Vector3.Dot(transForward, ray.direction));算出射线碰触到该 UI 的精确距离。

  3. 物理墙壁剔除if (distance >= hitDistance) continue;—— 墙在距离 5 的地方,UI 在距离 10 的地方,UI 被墙挡住,剔除!

  4. 生成结果:将通过所有测试的 UI 包装成RaycastResult结构体,塞入resultAppendList,交给EventSystem去做最终排序。

三、 内部几何检测:私有Raycast方法解析

private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList<Graphic> foundGraphics, List<Graphic> results)

在这个循环里,它对画布上的每一个开启了RaycastTarget的图像进行4 重过滤

  1. 状态过滤

    if (!graphic.raycastTarget || graphic.canvasRenderer.cull || graphic.depth == -1)

    如果目标没开射线检测、或者被系统剔除了、或者还没被渲染过,跳过。

  2. 矩形包围盒过滤 (Rect Transform Check)

    if (!RectTransformUtility.RectangleContainsScreenPoint(...))

    这是最高效的数学过滤。计算鼠标坐标是否落在 UI 元素的矩形框(RectTransform)范围内。如果连矩形框都没进,直接跳过。

  3. 远裁剪面过滤

    if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane)

    如果 UI 跑到了相机视距之外,跳过。

  4. 像素级/高级过滤 (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的设计哲学

  1. 不是真物理,而是纯数学:它并没有使用 Unity 的 Collider(碰撞体)来检测点击。对于 UI 来说,碰撞体太重了。它全程使用的是RectTransform的几何边界计算(包围盒判断)和射线平面交点数学公式,性能极高。

  2. 层层漏斗,尽早剔除:它不会上来就算复杂的 3D 距离。而是先查屏幕区域 -> 再查包围盒 -> 再查像素 Alpha -> 最后查 3D 遮挡。通过一步步的漏斗,把大部分无效计算在前期就扼杀掉。

  3. 完美的解耦:它通过GraphicRegistry获取目标,而不是自己去GetComponentsInChildren。它把检测结果塞给EventSystem,自己绝不越俎代庖去触发点击事件。分工极其明确。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询