解释EventSystem的射线投射排序规则

解读

在国内 Unity 面试中,这道题常被用来区分“只会拖 UI”与“真正做过交互底层”的候选人。面试官想听你回答三层含义:

  1. 射线到底怎么打——EventSystem 依赖 GraphicRaycaster 还是 PhysicsRaycaster?
  2. 打到的结果如何排序——距离、层级、RenderOrder、模块优先级谁说了算?
  3. 排序后如何挑中唯一目标——First Raycast All,再 Sort,再 Send Pointer Event 的完整链路。

答出“先相机深度、再距离、再层级”只能拿 60 分;把 ScreenSpaceOverlay > ScreenSpaceCamera > WorldSpace 的相机权重、SortingGroup/OrderInLayer 的插队逻辑、RaycastResult.sortOrderDepth 的源码级字段都说清,才能拿到 90+。

知识点

  1. RaycastComparer 静态类:EventSystem 内部用 Array.Sort 的自定义 comparer,比较顺序固定为:
    • camera.depth 升序(Overlay 相机 depth 固定 −10000,永远最优先)
    • sortOrder(Canvas.sortingOrder 或 SortingGroup.sortingOrder)
    • orderInLayer(SpriteRenderer/Canvas 内层)
    • distance 升序(到射线原点的距离,UI 射线取 z=0 平面距离)
    • depth(Graphic.depth,同一 Canvas 内 UI 元素绘制顺序)
    • index 升序(RaycastAll 返回数组下标,保证稳定性)
  2. 模块优先级:GraphicRaycaster 结果始终排在 PhysicsRaycaster 之前,因为 RaycastModule.sortOrderPriority 默认为 0,而 Physics 模块为 −1000。
  3. 穿透阻断:一旦 GraphicRaycaster 找到 blocker(raycastTarget=true 且位于同一 Canvas),后续 Physics 对象即使更近也会被截断;可通过 RaycastFilterType.BlockingObjects 开关控制。
  4. 重写规则:实现 IEventSystemHandler 的脚本可监听 OnBeginDrag 等事件,但排序阶段无法干预;若想自定义排序,需继承 BaseRaycaster 并重写 SortRaycastResults 方法,国内项目常用于“3D 场景与 UI 混合点击”需求。

答案

EventSystem 的射线投射排序由 RaycastComparer 完成,规则可背成“五键升序”:

  1. camera.depth 最小者优先(Overlay 相机永远最小)
  2. sortingOrder 小者优先
  3. orderInLayer 小者优先
  4. distance 小者优先
  5. graphic.depth 小者优先(同一 Canvas 内)
  6. index 小者优先(保证同条件稳定)

最终 RaycastAll 列表首元素 被 EventSystem 选为 pointerCurrentRaycast,后续模块不再处理;若需让 3D 物体“挡在 UI 前面”,要么把 UI 放到 ScreenSpaceCamera 并降低相机 depth,要么给 3D 物体挂 PhysicsRaycaster 并关闭 GraphicRaycaster 的 Blocking Mask。

拓展思考

  1. 国内手游常见“模型穿透 UI”需求:把主 UI 画布设 ScreenSpaceCamera,depth=1,3D 场景相机 depth=0,这样 camera.depth 规则 让 UI 永远压在最上;若想让某个 3D 弹窗模型盖住 UI,只能临时把模型渲染到 RenderTexture 再显示到 UI 层,不能靠改 distance
  2. 热更新框架(如 XLua、ILRuntime)下,无法直接继承 BaseRaycaster,可通过 EventSystem.RaycastAll 手动调用后重写排序列表,再调用 ExecuteEvents.ExecuteHierarchy,实现 Lua 侧完全接管点击逻辑,避免版本差异。
  3. 性能陷阱:ScrollRect 里大量 raycastTarget=true 的文本,会在滑动时每帧触发 RaycastAll;国内项目常规优化是 动态关闭非可视区域元素的 raycastTarget,或把 Canvas 拆成“静态”与“可交互”两层,减少排序长度。