前言
现在说webVr可能不太合适,正确应该叫webXR API,不过下面的内容确实是跟VR设备相关,以Threejs为例,事实上webxr是个标准,跟具体3D引擎库没关系
一、扳机键和抓取键
对于threejs来说,当前能直接使用接口获取手柄交互的有两个,分别是扳机键和抓取键,下面的代码表示扳机键和抓取键按下和松开的事件,这两个按钮在大部分情况下足够了。其实事件参数event里面是有变量表示圆盘(摇杆)键的数值的,但是从这里获取就意味着必须按一次扳机/抓取键才能获取,不靠谱
this.controller1 = this.renderer.xr.getController(0)
this.controller1.addEventListener('selectstart', onSelectStart) //select--扳机键
this.controller1.addEventListener('selectend', onSelectEnd)
this.controller1.addEventListener('squeezestart', onSqueezeStart)//squeeze--抓取键
this.controller1.addEventListener('squeezeend', onSqueezeEnd)
function onSelectStart(event) {
console.log('onSelectStart', event)
}
function onSelectEnd(event) {
console.log('onSelectEnd', event)
}
function onSqueezeStart(event) {
console.log('onSqueezeStart', event)
}
function onSqueezeEnd(event) {
console.log('onSqueezeEnd', event)
}
二、其他按键
经过一番搜索,发现其实是能获取到输入源(inputSources)列表的,如下面的代码,可以在手柄接入时自动调用,获取手柄列表,inputSource里有个属性gamepad可获取手柄的绝大多数按键,比如圆盘/摇杆键,扳机键,抓取键,一体机手柄的A、B键等等(事实上一体机还能获取到两个菜单按键,但这两个按键在浏览器已经被使用了),对于这些按键的监听我使用的是每帧循环检测(应该有更高效的方式,奈何本人太菜)
session.addEventListener('inputsourceschange', event => {
console.log(event)
var inputSourceList = event.session.inputSources
//可以通过回调函数将inputSourceList传递到其他脚本
})
这里的代码没有检测扳机键和抓取键,这两个按键感觉用上面的事件监听更高效,具体也可看下代码里的注释。这个函数需要每帧调用
/**
* 处理手柄事件
*/
handleEvent() {
if (this.vrInputSource && this.vrInputSource.length > 0) {
this.vrInputSource.forEach(xrInputSource => {
if (xrInputSource.gamepad) {
//通过navigator.userAgentData.platform == 'Windows'属性来判断是一体机还是主机式设备
//我项目用的是HTC,所以这里是HTC的检测方式,其他设备类似
if (this.isWindows) {
// htc圆盘键按下时除了需要检测axes外还需要检测第三个按钮是否按下(第一个按钮是扳机键,第二个是抓取键)
if (xrInputSource.gamepad.buttons.length != 3) {
console.log('不是HTC设备!')
} else {
if (!xrInputSource.lastAxes) {
xrInputSource.offset = 0.8
xrInputSource.lastAxes = new Vector2(0, 0)
xrInputSource.buttonStates = false
}
if (
xrInputSource.gamepad.axes[0] > xrInputSource.offset &&
xrInputSource.lastAxes.x <= xrInputSource.offset &&
xrInputSource.gamepad.buttons[2].pressed &&
!xrInputSource.buttonStates
) {
xrInputSource.lastAxes.x = xrInputSource.gamepad.axes[0]
console.log('按下右键')
} else if (
xrInputSource.gamepad.axes[0] < -xrInputSource.offset &&
xrInputSource.lastAxes.x >= -xrInputSource.offset &&
xrInputSource.gamepad.buttons[2].pressed &&
!xrInputSource.buttonStates
) {
xrInputSource.lastAxes.x = xrInputSource.gamepad.axes[0]
console.log('按下左键')
}
if (
xrInputSource.gamepad.axes[1] > xrInputSource.offset &&
xrInputSource.lastAxes.y <= xrInputSource.offset &&
xrInputSource.gamepad.buttons[2].pressed &&
!xrInputSource.buttonStates
) {
xrInputSource.lastAxes.y = xrInputSource.gamepad.axes[1]
console.log('按下下键')
} else if (
xrInputSource.gamepad.axes[1] < -xrInputSource.offset &&
xrInputSource.lastAxes.y >= -xrInputSource.offset &&
xrInputSource.gamepad.buttons[2].pressed &&
!xrInputSource.buttonStates
) {
xrInputSource.lastAxes.y = xrInputSource.gamepad.axes[1]
console.log('按下上键')
}
xrInputSource.buttonStates = xrInputSource.gamepad.buttons[2].pressed
// 没有按下时重置轴记录 令其可连续点击
if (!xrInputSource.buttonStates) {
xrInputSource.lastAxes.x = 0
xrInputSource.lastAxes.y = 0
}
}
}
// pico 或其他3Dof设备(只测试了pico )
else {
if (!xrInputSource.lastAxes) {
xrInputSource.offset = 0.8
xrInputSource.lastAxes = new Vector2(0, 0)
xrInputSource.buttonStates = {}
}
// pico 的axes有4个元素,但只有后两个有效
if (xrInputSource.gamepad.axes[2] > xrInputSource.offset && xrInputSource.lastAxes.x <= xrInputSource.offset) {
//console.log('按下左键')
//makeSystemTip函数是自己写的在一体机里弹出一个面板,显示文字信息
//xrInputSource.handedness表示了手柄的左右之分(HTC的是先接入的是右手(惯用手))
this.makeSystemTip(xrInputSource.handedness + '按下右键', 1000)
}
if (xrInputSource.gamepad.axes[2] < -xrInputSource.offset && xrInputSource.lastAxes.x >= -xrInputSource.offset) {
//console.log('按下右键')
this.makeSystemTip(xrInputSource.handedness + '按下左键', 1000)
}
if (xrInputSource.gamepad.axes[3] > xrInputSource.offset && xrInputSource.lastAxes.y <= xrInputSource.offset) {
//console.log('按下上键')
this.makeSystemTip(xrInputSource.handedness + '按下下键', 1000)
}
if (xrInputSource.gamepad.axes[3] < -xrInputSource.offset && xrInputSource.lastAxes.y >= -xrInputSource.offset) {
//console.log('按下下键')
this.makeSystemTip(xrInputSource.handedness + '按下上键', 1000)
}
xrInputSource.lastAxes.x = xrInputSource.gamepad.axes[2]
xrInputSource.lastAxes.y = xrInputSource.gamepad.axes[3]
// pico 的按钮有6个,第一二个是扳机键和抓取键,第五六个是A、B键,中间两个是菜单键,已经被设备托管了,不能使用
if (xrInputSource.gamepad.buttons.length == 6) {
if (xrInputSource.gamepad.buttons[4].pressed && !xrInputSource.buttonStates['4']) {
//console.log('按下B键')
this.makeSystemTip(xrInputSource.handedness + '按下A键', 1000)
}
if (xrInputSource.gamepad.buttons[5].pressed && !xrInputSource.buttonStates['5']) {
//console.log('按下A键')
this.makeSystemTip(xrInputSource.handedness + '按下B键', 1000)
}
xrInputSource.buttonStates['4'] = xrInputSource.gamepad.buttons[4].pressed
xrInputSource.buttonStates['5'] = xrInputSource.gamepad.buttons[5].pressed
}
}
}
})
}
}
参考
https://developer.mozilla.org/zh-CN/docs/Web/API/WebXR_Device_API
https://developer.mozilla.org/en-US/docs/Web/API/XRInputSource
https://developer-cn.pico-interactive.com/document/web/development-platform/#7e7428a1