Unity VR开发中如何通过VR控制器触发UI Button的onClick()事件
以下方案基于目前Unity官方推荐的OpenXR + XR Interaction Toolkit(2.0及以上版本)实现,兼容绝大多数主流VR头显,不需要额外引入第三方SDK。
前置配置先做对,不然写了代码也跑不通
- 项目先导入XR Plugin Management、OpenXR、XR Interaction Toolkit三个官方包,完成XR运行时配置,给左右控制器挂好对应的XR Controller组件。
- 场景里的EventSystem删除默认的Standalone Input Module,换成XR UI Input Module;所有需要交互的UI Canvas把Render Mode改成World Space,挂上Tracked Device Graphics Raycaster组件,这是VR下UI能被射线识别的基础。
- 默认OpenXR输入映射里,左右控制器的主键(比如Quest手柄的X/A键、Index手柄的对应主键)已经绑定到Primary Button输入动作,不需要自己重新做输入绑定。
最推荐方案:零侵入实现,不改动现有Button的事件绑定
这个方案不需要你改之前给Button.onClick绑的任何逻辑——不管是Inspector里拖的方法,还是代码里动态加的监听,都能正常触发,和鼠标点击的效果完全一致。核心逻辑就是:主键按下时,检测当前控制器的UI射线是不是悬停在可交互的Button上,如果是就手动触发按钮的点击事件。
把下面的脚本挂到左右控制器对象上:
using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.UI; using UnityEngine.XR.Interaction.Toolkit; public class VRPrimaryButtonUI : MonoBehaviour { [Header("把对应组件拖到这几个字段里")] [SerializeField] private XRRayInteractor uiRayInteractor; [SerializeField] private InputActionReference primaryButtonAction; private void OnEnable() { primaryButtonAction.action.performed += TriggerButtonClick; } private void OnDisable() { primaryButtonAction.action.performed -= TriggerButtonClick; } private void TriggerButtonClick(InputAction.CallbackContext context) { // 检测当前射线是否命中UI元素 if (!uiRayInteractor.TryGetCurrentUIRaycastResult(out var uiHit)) return; // 从命中的对象往上找对应的Button组件,适配按钮上挂了子物体文本、图标的情况 Button targetButton = uiHit.gameObject.GetComponentInParent<Button>(); // 只有按钮存在且处于可交互状态才触发 if (targetButton != null && targetButton.interactable) { targetButton.onClick.Invoke(); // 可选:触发按钮的点击状态反馈,和鼠标点击的视觉效果一致 targetButton.OnPointerClick(new PointerEventData(EventSystem.current)); } } }
脚本挂好之后的配置步骤:
- 左右控制器各挂一份脚本,把控制器自身挂载的、用来做UI交互的XR Ray Interactor拖到uiRayInteractor字段
- primaryButtonAction字段分别选对应左右手的Primary Button输入动作即可
简化场景方案:不需要射线悬停,按键直接触发固定按钮
如果你的需求是不管射线指在哪,按下主键就固定触发某个按钮(比如全局确认键),直接用更简单的逻辑就行,不需要做射线检测:
using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.UI; public class FixedButtonBind : MonoBehaviour { [SerializeField] private Button targetButton; [SerializeField] private InputActionReference primaryButtonAction; private void OnEnable() { primaryButtonAction.action.performed += _ => { if (targetButton.interactable) targetButton.onClick.Invoke(); }; } private void OnDisable() { primaryButtonAction.action.performed -= _ => { if (targetButton.interactable) targetButton.onClick.Invoke(); }; } }
几个容易踩的坑
- 不要直接在输入回调里写业务逻辑,一定要走Button的onClick.Invoke()调用,这样按钮的禁用状态、事件触发顺序、点击反馈都会和正常点击保持一致,不会出现按钮灰了按了还触发逻辑的问题。
- 如果项目用的是SteamVR SDK,只要把输入检测的部分换成SteamVR对应的按键按下判定即可,核心的Button查找、onClick触发逻辑完全通用。
- 检查Tracked Device Graphics Raycaster的事件遮罩设置,不要把UI所在的层给屏蔽了,不然射线检测不到按钮。
内容的提问来源于stack exchange,提问作者BigShaqSkrrr
相关产品推荐
相关产品推荐

