Awake 里最先做的一件事
源码中 Awake() 的顺序很值得注意:
- 先
InitializeReferences() - 再
base.Awake()
这意味着它先把自己依赖的核心组件找齐,再走基类初始化流程。这保证 attachTransform 在早期就准备好。
缺少核心组件时会自动补
InitializeReferences() 做三件事:
- 远距离 Caster:
farInteractionCaster为空时,先找当前对象已有的ICurveInteractionCaster,找不到就AddComponent<CurveInteractionCaster>()。 - 近距离 Caster:
nearInteractionCaster为空时,先找当前对象已有的IInteractionCaster,但跳过同时也是ICurveInteractionCaster的组件——避免把远距 Caster 误当近距 Caster。最终找不到就AddComponent<SphereInteractionCaster>()。 - Attach Controller:
interactionAttachController为空时,先找IInteractionAttachController,找不到就AddComponent<InteractionAttachController>()。
最后还会执行:
attachTransform = interactionAttachController.GetOrCreateAnchorTransform();
附着锚点不是可有可无的,会在初始化阶段被明确创建或拿到。
UI 交互不是单纯一个布尔值
enableUIInteraction 的 setter 不只是把布尔值改掉,还会调用:
RegisterOrUnregisterXRUIInputModule(m_EnableUIInteraction)
UI 交互开关不只是逻辑判断,还会影响和 XRUIInputModule 的注册关系。改这个值时,系统会同步处理 UI 注册状态。
OnEnable / OnDisable 做了什么
OnEnable:如果 UI 交互开启,执行 UpdateUIRegistration()。
OnDisable:
- 取消向
XRUIInputModule的注册 - 如果启用了 UI Toolkit 世界空间支持,也会从对应系统中注销
- 调用
InitializeInteractor()清空本轮交互缓存
它对 UI 注册状态的管理比较完整,不是只在首次启动时注册一次就不管了。
每一帧的核心流程
在 PreprocessInteractor(UpdatePhase.Dynamic) 里,核心顺序是:
InitializeInteractor()UpdateAnchor()DetermineSelectionRegion()EvaluateNearInteraction()EvaluateFarInteraction(newSelectionRegion)UpdateSelectionRegion(newSelectionRegion)- 如启用 UI Toolkit 支持,再处理对应事件
这条顺序告诉我们:
- 先更新附着点
- 再判断当前是 Near 还是 Far
- 再依次评估近距和远距命中
selectionRegion 是怎么判断的
DetermineSelectionRegion() 的逻辑很直接:
- 如果当前没有选择,返回
None - 如果有选择,看
interactionAttachController.hasOffset - 有偏移就是
Far - 没偏移就是
Near
一个很关键的行为:
某个对象最开始可能是通过远距离选中的,但如果它被拉近到没有偏移,selectionRegion会从Far变成Near。
这也是官方文档里"Far 选中后可能变成 Near"的源码依据。
近距和远距的优先关系
近距逻辑
EvaluateNearInteraction() 里,如果满足下面任一条件,就不会继续:
- 没开启近距探测
- 当前已经有选中对象
- 近距 Caster 没找到有效 Collider
近距逻辑更偏向"未选中时找手边目标",不是"选中了还一直靠近重新抢目标"的设计。
远距逻辑
EvaluateFarInteraction() 里有两个关键条件:
- 如果没开远距探测,且当前也不是 Far 选中,就不更新远距
- 如果近距已经先找到有效目标,则直接不处理远距
这说明:
同一帧里,如果近距先拿到有效目标,远距通常让路。
手边真有能抓的东西时,优先按近距处理——不要让远处射线抢走焦点。
它会比较 2D UI 命中和 3D 命中
远距逻辑不是只看 3D Collider。它还会:
- 取当前 UI 的
RaycastResult - 计算 UI 命中距离
- 计算 3D 命中距离
- 比较两者谁更应该被处理
更容易理解地说:
它不是"只要打到 3D 就不管 UI",而是会在 UI 和 3D 之间做优先级比较。
对同时存在 World Space UI 与 3D 模型的 VR 场景很重要。
目前只支持一个有效目标
源码里有一个明确字段:
m_AllowMultipleValidTargets = false
注释写得很清楚:当前只支持一个有效目标,未来可能扩展。它内部虽然可能暂存多个候选,但对外主要按"单一有效目标"工作。
排序和过滤的一个细节
近距和远距目标注册时都会考虑 targetFilter。如果有 targetFilter,很多情况下排序会交给 filter;sortNearTargetsAfterTargetFilter 只适合"filter 本身不排序"的情况。
这个选项不是默认都该开,是给进阶过滤逻辑准备的。
关键结论
- 运行时确实会自动补默认组件,但项目里最好还是显式挂好,方便排查。
- UI 交互开关会影响注册状态,不只是变量值。
- 近距优先级通常高于远距。
- 远距逻辑会同时权衡 UI 命中和 3D 命中。
selectionRegion会随着对象被拉近而变化。