☰
react-360 输入系统解析:vr-input-source 包如何跟踪 VR 手柄并触发 select / press 事件
2026/9/25 7:31:10 网站建设 项目流程
  • 前端
  • 3D渲染

【免费下载链接】react-360

Create amazing 360 and VR content using React

项目地址:https://gitcode.com/gh_mirrors/re/react-360
点击查看免费下载

导读

vr-input-source是 react-360 中负责 VR 手柄(positionally-tracked gamepads)跟踪与事件派发的核心基础包。它把 WebVR 时代依赖轮询的 Gamepad API 封装成类似未来 WebXRXRInputSource的事件式接口:持续跟踪所有带有物理姿态的手柄、检测按钮按下/释放并派发selectstart/press等事件,同时记录“最近一次被使用”的手柄作为 Active Input Source,供应用决定哪只手柄负责绘制光标。读完本文,你将掌握该包的完整事件模型、主/次按钮判定机制、与 react-360 上层(射线检测、光束渲染、点击分发)的集成方式,以及如何扩展WellKnownGamepads映射来支持新设备。

包定位与核心职责

vr-input-source位于 packages/vr-input-source,其 README 将自身职责概括为三项:

  1. 跟踪所有上报物理位置的手柄:只关注gamepad.pose存在(即位置追踪)的设备,忽略普通无姿态手柄;
  2. 在按钮按下/释放时触发事件:由于 Web Gamepad API 采用轮询而非事件驱动,需要每帧检查手柄状态并与上一帧做差分;
  3. 维护 Active Input Source:多手柄(如 Oculus Touch 双手柄)同时使用时,跟踪“最近使用”的一个,典型用途是让它控制光标,这是 VR 平台上的常见交互模式。

包的package.json(packages/vr-input-source/package.json)声明其描述为 “Gamepad input collection for WebVR applications”,版本 0.3.0,对外入口是 index.js,它直接 re-export 了dist/VRInputSource编译产物。

事件模型总览

包对外暴露两类事件,全部常量定义在 InputEventTypes.js:

事件触发时机类比
selectstart手柄任一主按钮被按下mousedown
selectend主按钮被释放,或手柄在按钮按下时断开mouseup
select主按钮按下后释放(完整一次点击)click
pressstart任意按钮(主或非主)被按下mousedown
pressend任意按钮被释放,或手柄在按钮按下时断开mouseup
press任意按钮按下后释放click
touchstart手指接触电容按钮/触控板—
touchend手指离开电容按钮/触控板—
inputsourcechange有姿态的手柄集合发生变化(连接或断开)—
activesourcechangeActive Input Source 改变(最近使用的手柄变化)—

其中前两类是顶层VRInputSource类的事件;inputsourcechange/activesourcechange是包级(Source)事件,常量定义同样位于该文件。

为什么需要这些事件:Gamepad API 的轮询缺陷

Web Gamepad API 本身是轮询式的——调用navigator.getGamepads()获取快照,不提供键盘/鼠标那样的回调事件。因此必须在每一渲染帧检查手柄状态,与上一帧比对以判断哪些按钮发生了变化。vr-input-source对所有被跟踪的手柄统一执行这个动作,派发上述事件。

主按钮事件(Primary Button Events)

主按钮是执行“选择/确认”动作的按钮,不同设备各不相同。README 特别建议:由于 VR 输入设备差异极大,开发者应围绕单一选择动作构建界面,而非依赖大量按钮的存在。三个 select 事件用于这类统一选择语义。

通用按钮事件(Alternative Button Events)

pressstart/pressend/press用于跟踪任意按钮(主或非主)的按下与释放,适合需要监听非主按钮(如菜单键、摇杆)的场景。

电容按钮事件(Capacitive Button Events)

部分手柄具有电容感应按钮或触控板,touchstart/touchend在手指接触/离开时触发。注意:从源码看,GamepadState中_lastButtons虽记录了touched字段,但update()的差分逻辑当前只基于pressed状态派发 select/press 事件(GamepadState.js),触控事件的完整派发链可视为 API 预留的扩展点,具体以各浏览器实现的手柄touched上报为准。

事件派发的底层实现:GamepadState 差分检测

GamepadState.js 是单个手柄的状态机,核心是update(gamepad)方法,每帧被调用一次(源码注释明确要求 “This should be called every frame”)。其差分逻辑:

  • 按下检测:button.pressed && !lastPressed时,若该按钮是主按钮则先发selectstart,再发pressstart(主按钮同时是“任意按钮”);
  • 释放检测:!button.pressed && lastPressed时,主按钮发selectend+select,随后发pressend+press;
  • 断开兜底:手柄断开时update(null),对所有仍处于按下状态的按钮补发 end 类事件,保证状态不悬挂——README 中selectend/pressend的定义也明确包含了“手柄在按钮按下时被断开”这一情形。

每个事件 payload 都是ButtonEvent:{gamepadIndex, index, primary},其中index是按钮在gamepad.buttons中的序号,primary标记该按钮是否被判定为主按钮。

顶层 VRInputSource 的职责

VRInputSource.js 是包的顶层类,职责包括:

  • 初始化扫描:构造时调用getGamepads()(优先navigator.getGamepads,回退webkitGetGamepads),只对带pose的手柄创建GamepadState,并记录为首个 Active Source;
  • 监听连接/断开:通过window上的gamepadconnected/gamepaddisconnected事件维护_gamepads数组,并派发inputsourceschange;断开时若该手柄正是活动源,会调用setActiveSource(-1)置空并触发activesourcechange(VRInputSource.js);
  • 活动源切换:在_fireCallbacks中,收到pressstart时自动把该手柄设为 Active Source(VRInputSource.js)——这就是“最近使用优先”语义的实现点;
  • 每帧刷新:update()遍历所有GamepadState调用其update(),上层渲染循环需每帧驱动它。

GamepadState还提供getPosition()/getOrientation()(姿态四元数[x,y,z,w])与isPositionEmulated(),供射线投射与手柄光束渲染使用。

主按钮 vs 次按钮:WellKnownGamepads 映射

手柄按钮的物理排布差异巨大:Daydream 只有一个按钮;Oculus Go 有一个按钮加一个扳机;Oculus Touch、Vive Wand、Windows MR 手柄则有多个按钮。而手柄上报的按钮顺序与其意图动作几乎无关。为此vr-input-source内置了知名手柄的映射表 WellKnownGamepads.js,将各设备的“主动作”映射到select事件。

现有映射(按gamepad.id字符串匹配):

手柄 ID主按钮映射
Gear VR Touchpad[true]
Gear VR Controller[true, true]
Oculus Go Controller[true, true]
Daydream Controller[true]
Oculus Touch (Left)/Oculus Touch (Right)[false, true, false, true, false](thumbstick、trigger、grip、A/X、B/Y)
Spatial Controller (Spatial Interaction Source) 045E-065B(标准 MS MR)[false, true, false, false, true]
Spatial Controller (Spatial Interaction Source) 045E-065D(Samsung Odyssey)同上

例如 Oculus Touch 的 A 按钮与主扳机都被映射为 primary,可触发select事件;而摇杆、握柄、B/Y 则不是。

判定逻辑在 detectPrimaryButtons.js:

  • 若gamepad.id命中WellKnownGamepads,直接返回对应数组;
  • 未识别手柄:默认把所有按钮都标记为 primary。这是刻意的未来兼容设计——避免新硬件发布后开发者不得不升级旧应用去添加映射。

因此,若你遇到未覆盖的新手柄且希望更精确的主按钮语义,只需在该表中按gamepad.id追加映射数组(README 明确说明该表可更新以支持新输入类型)。

姿态数据与位置模拟

GamepadState构造函数从gamepad.pose读取orientation/position。当设备只有朝向没有位置(pose.hasPosition为假)时,会调用 emulatePosition.js 用虚拟手臂模型估算位置:将手臂向量(0, 0, -0.4)按手柄朝向四元数旋转,再根据hand === 'left'在 x 轴偏移-0.3/0.3,y 轴统一偏移-0.3,模拟出位于身体前方的手部位置。因此:

  • getPosition()返回的可能是模拟位置而非真实传感器数据;
  • 通过isPositionEmulated()可判断位置是否来自虚拟手臂模型;
  • 这种机制让只有 3DoF 朝向追踪的设备(如 Daydream)也能参与射线/光标交互,无需绝对位置。

在 react-360 中的集成:从事件到光标与点击

vr-input-source并非孤立工具,它是 react-360 控制器体系的底座,上层消费方式可从以下源码印证:

  • 点击分发:packages/react-360/src/Container.js 中,vrInputSource.addEventListener('selectstart' / 'selectend')会向所有 Surface 的 ReactRoot 派发input事件(buttonClass: 'confirm',action: 'down'/'up'),与鼠标的mousedown/mouseup路径并行——这就是手柄选择按钮驱动 UI 点击的实现;
  • 活动源追踪光标:ControllerModel.js 监听activesourcechange,当gamepadIndex < 0(活动源断开/清空)时隐藏手柄光束模型,否则显示——即“最近使用手柄绘制光束”;
  • 射线检测:ControllerRaycaster.js 通过getActiveSource()获取当前活动GamepadState,用getOrientation()旋转出射线方向、用getPosition()作为射线原点,实现手柄指向的命中检测;其drawsCursor()返回true,表明它负责绘制光标。

这一调用链完整回答了 README 所述“用活动手柄控制光标”的具体落点:selectstart→ React 层input事件;activesourcechange→ 光束显隐;getActiveSource()+ 姿态/位置 → 射线方向与原点。

使用要点与最佳实践

综合 README 与源码,在 react-360 应用中使用该包的核心实践:

  1. 围绕单一选择语义设计界面:不同手柄按钮排布差异巨大,统一监听selectstart/selectend/select即可覆盖绝大多数设备,无需关心具体是哪个按钮;
  2. 每帧驱动update():事件派发依赖帧级差分,渲染循环需持续调用VRInputSource.update(),否则按钮状态不会更新;
  3. 善用press系事件跟踪非主按钮:需要响应菜单键、摇杆等次要输入时使用pressstart/press/pressend;
  4. 监听inputsourceschange感知设备变化:连接/断开手柄时由此事件获知,并据此更新 UI 提示或重新校准交互;
  5. 监听activesourcechange切换光标持有者:双手柄场景下让“最近使用”的手柄接管光标,符合主流 VR 平台交互习惯;
  6. 扩展WellKnownGamepads支持新硬件:在 WellKnownGamepads.js 按gamepad.id追加映射即可;未识别设备默认全按钮 primary 的兜底策略保证了向前兼容;
  7. 区分真实/模拟位置:对仅有朝向追踪的设备,getPosition()返回虚拟手臂模拟值,可结合isPositionEmulated()决定是否用于精细交互。

小结

vr-input-source通过“轮询差分 + 事件化”两层设计,把碎片化的 Gamepad API 抽象成统一、面向选择语义的输入模型:selectstart/selectend/select屏蔽设备差异,press系列覆盖任意按钮,activesourcechange解决多手柄光标归属,WellKnownGamepads提供可扩展的主按钮映射。在 react-360 中它直接驱动点击分发、手柄光束与射线检测,是理解整个 VR 交互管线的重要入口。相关源码可继续深入查阅 packages/vr-input-source/src 与 packages/react-360/src/controller。

  • 前端
  • 3D渲染

【免费下载链接】react-360

Create amazing 360 and VR content using React

项目地址:https://gitcode.com/gh_mirrors/re/react-360
点击查看免费下载

相关推荐

上一篇:如何快速掌握Big Transfer (BiT):通用视觉表示学习工具完全指南
下一篇:Mylar3 开源项目安装与使用指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询