- 前端
- 3D渲染
【免费下载链接】react-360
Create amazing 360 and VR content using React
导读
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 将自身职责概括为三项:
- 跟踪所有上报物理位置的手柄:只关注
gamepad.pose存在(即位置追踪)的设备,忽略普通无姿态手柄; - 在按钮按下/释放时触发事件:由于 Web Gamepad API 采用轮询而非事件驱动,需要每帧检查手柄状态并与上一帧做差分;
- 维护 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 | 有姿态的手柄集合发生变化(连接或断开) | — |
activesourcechange | Active 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 应用中使用该包的核心实践:
- 围绕单一选择语义设计界面:不同手柄按钮排布差异巨大,统一监听
selectstart/selectend/select即可覆盖绝大多数设备,无需关心具体是哪个按钮; - 每帧驱动
update():事件派发依赖帧级差分,渲染循环需持续调用VRInputSource.update(),否则按钮状态不会更新; - 善用
press系事件跟踪非主按钮:需要响应菜单键、摇杆等次要输入时使用pressstart/press/pressend; - 监听
inputsourceschange感知设备变化:连接/断开手柄时由此事件获知,并据此更新 UI 提示或重新校准交互; - 监听
activesourcechange切换光标持有者:双手柄场景下让“最近使用”的手柄接管光标,符合主流 VR 平台交互习惯; - 扩展
WellKnownGamepads支持新硬件:在 WellKnownGamepads.js 按gamepad.id追加映射即可;未识别设备默认全按钮 primary 的兜底策略保证了向前兼容; - 区分真实/模拟位置:对仅有朝向追踪的设备,
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
相关推荐
InvenTree开源库存管理系统:从数据孤岛到智能供应链的3个技术跃迁
InvenTree开源库存管理系统:从数据孤岛到智能供应链的3个技术跃迁 InvenTree是一款基于Python/Django构建的开源库存管理系统,专为电子
后端前端企业应用ERP如何高效处理Unity中的多设备输入?FairyGUI-unity事件系统终极指南
如何高效处理Unity中的多设备输入?FairyGUI unity事件系统终极指南 FairyGUI unity是一款灵活的UI框架,专为Unity开发打造,提
前端UI组件游戏开发React 360事件处理机制:点击、悬停与VR输入源全解析
React 360事件处理机制:点击、悬停与VR输入源全解析 你是否还在为VR应用中的交互逻辑感到困惑?用户通过键盘、鼠标、手柄等多种设备与应用交互时,如何统一
前端3D渲染
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考