Zion插件分析——手柄
发表于2016-10-31
在Zion插件中,也对其做了细分,如下面的UML类图,所有手柄的基类都是ZionControllerBase,而可追踪的手柄,则继承自它,ZionTrackedControllerBase为所有可追踪手柄的基类,它与其他手柄派生类的主要区别是,要提供速度和角速度的接口。
一、按键映射
无论是否可以追踪,手柄类最重要的实现是获取按键的状态,由于各平台的手柄,按键都有不同,底层返回的掩码与虚拟按键的映射也有不同,因此在兼容的时候我们需要先把这些映射,转换为我们自己定义的按键映射,再进行状态的判断。
映射关系
控制器返回的按键状态是通过一个64位整型返回的,每一位表示某个按键的状态。因此在各平台手柄类中,都有各按键对应的掩码变量,如SteamVR平台,按键掩码以ulong常量枚举。
如下表列出的,按键对应的位数(从右往左数)。
那么假设底层返回的按键状态码为000…000101(),用它与各按键对应的掩码进行&运算,如果得到的不是0(即有一位匹配),就能得到响应按键的状态,例如与System(000…000001)进行&运算,最后一位匹配,说明System键当前是Press状态。
因此,获取按键状态的接口就可以这样写,state.ulButtonPressed是底层返回的状态码,buttonMask是我们想知道的按键的掩码。
1 2 3 4 5 | public bool GetPress( ulong buttonMask) { Update(); return (state.ulButtonPressed & buttonMask) != 0; } |
映射转换
既然各平台都有上述的按键映射关系,但是我们不可能在自己的插件里为所有设备的按键都一一进行这样的映射。我们只会针对特定功能,做一个广泛的集合:
那么我们就需要做一次映射的转换,将平台底层状态码与其掩码比对后,转换为我们需要的兼容版本的的状态码。以实现类ZionTrackedControllerSteamVR为例:
1 | public const ulong Enter = (1ul << ( int )EVRButtonId.k_EButton_SteamVR_Trigger); // 令手柄的扳机为Enter键 |
接下来,将真实按键比对的结果转换为我们自定义的状态码。
1 | if ((state.ulButtonPressed & Button.Enter) != 0) ulButtonPressed |= ( ulong )Zion.ZionController.Button.Enter; |
可以看到,state.ulButtonPressed & Button.Enter是利用SteamVR平台的掩码和底层返回结果进行比对,如果比对成功,那么就给我们自己的状态码ulButtonPressed相应的位上置1,具体是哪个位,就要看后面的Zion.ZionController.Button.Enter,在Zion插件里,Enter键是“10”,那么此时状态码ulButtonPressed的右数第二位就置为1。然后利用这个转换后的状态码,在上层我们获取按键状态时,用自定义的状态码和自定义的按键掩码对比即可。
经过这样的转换,就可以实现,定义一套按键映射,匹配各平台的按键关系。
二、手柄基类:ZionControllerBase
ZionControllerBase是所有支持手柄的基类,它提供了接口,获取按键状态以及轴数据,它的派生类需要实现各自的按键映射,返回状态码;以及实现自己的震动功能。
字段
ulButtonPressed:当前按键状态码
ulButtonPressedPrev:上一帧按键状态码
ulButtonTouched:当前触摸状态码
ulButtonTouchedPrev:上一帧触摸状态码
vAxes:轴数据数组
ulValidAxes:轴数据状态码
构造方法
ZionControllerBase ()
可供重写的虛方法
1、Update(ref ulong ulButtonPressed, ref ulong ulButtonTouched, ref Vector2[] vAxes, ref ulong ulValidAxes)
需要各平台根据自己的特性进行重写,进行转换后,返回按键、触摸板的状态码,以及所有轴数据。
2、Vibrate(ZionController.Button button, uint duration, float frequency, float amplitude)
需要各平台根据自己的特性进行重写,震动手柄,不同平台对震动参数要求不一样,不关注的参数可以忽略。
3、Update()
不是Unity的Monobehaviour方法。最终在Monobehaviour类,ZionRender.cs的Update()中调用。
将这旧的按键状态码,触摸板状态码保存在xxxPrev参数中,重新获取新的状态码和轴数据。
按键回调方法
Notify(ButtonDownHandler buttonDown, ButtonUpHandler buttonUp,TouchDownHandler touchDown, TouchUpHandler touchUp, AxisDataHandler axisData)
普通手柄按键回调,轮询所有已定义的按键及轴数据状态,发送对应事件。
Notify(ZionController.Index index, TrackedButtonDownHandler buttonDown, TrackedButtonUpHandler buttonUp,TrackedTouchDownHandler touchDown, TrackedTouchUpHandler touchUp, TrackedAxisDataHandler axisData)
跟踪手柄按键回调,轮询所有已定义的按键及轴数据状态,发送对应事件。
实例方法
GetButton(ZionController.Button id):获取按键持续按下状态
GetButtonDown(ZionController.Button id):获取按键首次按下状态
GetButtonUp(ZionController.Button id):获取按键松开状态
GetTouch(ZionController.Button id):获取按键持续触摸状态
GetTouchDown(ZionController.Button id):获取按键首次触摸状态
GetTouchUp(ZionController.Button id):获取按键离开触摸状态
GetAxis(ZionController.Button id, ref Vector2 axis):获取轴数据
三、跟踪手柄类
1、跟踪手柄基类:ZionTrackedControllerBase
派生自手柄基类ZionControllerBase,但是由于跟踪手柄是红外线定位,所以提供接口,获取速度与角速度。
字段
velocity:手柄的速度
angularVelocity:手柄的角速度
上述参数都由派生类赋值
构造方法
ZionTrackedControllerBase ()
可供重写的虛方法
SetIndex(ulong index)
更新手柄索引,派生类需要根据具体平台的实现处理左右手柄,将Zion的索引转换为自己平台的索引
GetVelocity()
获取速度,即字段velocity的值。
GetAngularVelocity()
获取角速度,即字段angularVelocity的值
2.、ZionTrackedControllerSteamVR
HTC Vive手柄的跟踪手柄实现类。
字段
index:手柄索引
pose:位姿,用于获取速度和角速度
构造方法
ZionTrackedControllerSteamVR ()
重写方法
SetIndex(ulong index)
将Zion插件里的索引转换为SteamVR平台的索引。
1 2 3 4 | ETrackedControllerRole role = (index == Zion.Constants.indexLeft ? ETrackedControllerRole.LeftHand : ETrackedControllerRole.RightHand); var system = OpenVR.System; this .index = system.GetTrackedDeviceIndexForControllerRole (role); Update ( ref ulong ulButtonPressed, ref ulong ulButtonTouched, ref Vector2[] vAxes, ref ulong ulValidAxes) |
更新按键、触摸板、轴设备的状态及轴数据。其中三个ulong类型参数,都要经过上面说的按键映射转换。
更新速度、角速度。
获取上述参数,需要从底层取两个东西,按键状态和位姿:1、VRControllerState_t state;2.TrackedDevicePose_t pose
调用OpenVR来获取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | var system = OpenVR.System; ... system.GetControllerStateWithPose(ETrackingUniverseOrigin.TrackingUniverseStanding, index, ref state, ref pose) ... // 普通按键映射转换 if ((state.ulButtonPressed & Button.DpadUp) != 0) ulButtonPressed |= ( ulong )Zion.ZionController.Button.DpadUp; ... // 触摸按键映射转换 if ((state.ulButtonTouched & Button.DpadUp) != 0) ulButtonTouched |= ( ulong )Zion.ZionController.Button.DpadUp; ... // 轴数据设置 vAxes[0].x = state.rAxis0.x; vAxes[0].y = state.rAxis0.y; vAxes[1].x = state.rAxis1.x; vAxes[1].y = state.rAxis1.y; ... // 轴状态 ulValidAxes |= ( ulong )Zion.ZionController.Button.Touchpad; ulValidAxes |= ( ulong )Zion.ZionController.Button.TriggerAxis; // 更新速度与角速度 velocity.Set(...); angularVelocity.Set(...); Vibrate(Zion.ZionController.Button button, uint duration, float frequency, float amplitude) 实现震动,frequency和amplitude参数在SteamVR平台无效,无需处理。 SteamVR平台只支持轴设备的震动,所以首先根据button判断是否是轴设备,是哪个轴设备 int axisid = -1; // 非轴设备统一定为-1 switch (button) { case Zion.ZionController.Button.Touchpad: axisid = ( int )(EVRButtonId.k_EButton_SteamVR_Touchpad - EVRButtonId.k_EButton_Axis0); break ; case Zion.ZionController.Button.TriggerAxis: axisid = ( int )(EVRButtonId.k_EButton_SteamVR_Trigger - EVRButtonId.k_EButton_Axis0); break ; } |
如此,得到axisid为-1的话就不必再继续,因为不是轴设备;如果不是-1,则调用OpenVR,传入轴设备索引,执行震动操作。
var system = OpenVR.System;
if (system != null) {
system.TriggerHapticPulse (index, (uint)axisid, (char)duration);
}
嵌套类型
class Button{}
SteamVR的按键掩码及映射
3、ZionTrackedControllerOculusVR
Oculus Touch手柄的实现类。
字段
controllerType:手柄类型(左/右),不同于SteamVR用index来区分手柄,Oculus Touch使用枚举类,因为它不止有左右之分,Oculus平台的手柄还支持Gamepad,Remote等。
preConnected:上一帧连接状态。
isConnected:当前连接状态。
buttonMap:button按键虚拟映射。
touchButtonMap:touchButton按键虚拟映射。
重写方法
SetIndex(ulong index)
将Zion插件里的索引转换为Oculus平台的OVRPlugin.Controller,手柄类型枚举。并初始化对应的按键虚拟映射(嵌套类构造方法传参)。
1 2 3 4 5 6 7 8 | if (index == Zion.Constants.indexLeft) { controllerType = OVRPlugin.Controller.LTouch; } else if (index == Zion.Constants.indexRight) { controllerType = OVRPlugin.Controller.RTouch; } Update( ref ulong ulButtonPressed, ref ulong ulButtonTouched, ref Vector2[] vAxes, ref ulong ulValidAxes) |
更新按键、触摸板、轴设备的状态及轴数据。其中三个ulong类型参数,都要经过上面说的按键映射转换。
更新手柄连接状态,发送Zion事件。
和SteamVR类似,也是从底层获取按键的状态state,但是这里要用自定义的方法将Thumbstick、Trigger、HandlerTrigger的轴数据值解析成键值,并填充到state.buttons。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | // 根据controllerType得到相应手柄的按键状态 OVRPlugin.ControllerState state = OVRPlugin.GetControllerState(( uint )controllerType); ... // 根据阈值,将轴数据转换为“按下”的状态码,填充给state.buttons OVRController.resolveAxis( ref state); ... // 普通按键映射转换 if ((state.Buttons & buttonMap.DpadUp) != 0) ulButtonPressed |= ( ulong )Zion.ZionController.Button.DpadUp; ... // 触摸按键映射转换 if ((state.Buttons & touchButtonMap.DpadUp) != 0) ulButtonTouched |= ( ulong )Zion.ZionController.Button.DpadUp; ... 获取轴数据时同样要区分左右,因为Oculus底层返回的轴数据是分好左右,一起传上来的;同样只支持两个轴设备:Thumbstick(摇杆)和Trigger(扳机): // 轴设备,只前两个有效,第一个表示touchpad,第二个表示trigger if (controllerType == OVRPlugin.Controller.LTouch) { vAxes[0].x = state.LThumbstick.x; vAxes[0].y = state.LThumbstick.y; vAxes[1].x = state.LIndexTrigger; vAxes[1].y = 0; } else { //右手柄轴数据填充 } ulValidAxes |= ( ulong )Zion.ZionController.Button.Touchpad; ulValidAxes |= ( ulong )Zion.ZionController.Button.TriggerAxis; Vibrate(Zion.ZionController.Button button, uint duration, float frequency, float amplitude) 实现震动,参数button和duration不必处理。 OVRPlugin.SetControllerVibration(( uint )controllerType, frequency, amplitude); GetVelocity() 重写方法,获取速度。 GetAngularVelocity() 重写方法,获取角速度。 从底层获取的是四元数,要转一下: angularVelocity.Set(q.x,q.y,q.z); 嵌套类 class ButtonMap{}普通按键映射 class TouchButtonMap{}触摸按键映射 |
上述嵌套类构造时需要传入手柄类型,按键映射会相应有改变。