1、我们先来将SteamVR_Unity_Toolkit.unitypackage这个包导入,Asset Store里免费下载
2、当Unity熟练到一定程度,事件与委托是必备的基础!所以本文默认我们已经走过基础阶段。交互设计的精华也在于事件与委托的巧妙设计与实现。VR里的交互更不例外
这篇博客,从SteamVR_Unity_Toolkit的代码里剖析下,它是怎么封装SteamVR的事件的。
3、看类VRTK_ControllerEvents.cs
namespace VRTK{using UnityEngine;using System.Collections;
public struct ControllerInteractionEventArgs{public uint controllerIndex;public float buttonPressure;public Vector2 touchpadAxis;public float touchpadAngle;}
public delegate void ControllerInteractionEventHandler(object sender, ControllerInteractionEventArgs e);
public class VRTK_ControllerEvents : MonoBehaviour{public enum ButtonAlias{Trigger,Grip,Touchpad_Touch,Touchpad_Press,Application_Menu}
public ButtonAlias pointerToggleButton = ButtonAlias.Grip;public ButtonAlias grabToggleButton = ButtonAlias.Trigger;public ButtonAlias useToggleButton = ButtonAlias.Trigger;public ButtonAlias menuToggleButton = ButtonAlias.Application_Menu;
public int axisFidelity = 1;
public bool triggerPressed = false;public bool triggerAxisChanged = false;public bool applicationMenuPressed = false;public bool touchpadPressed = false;public bool touchpadTouched = false;public bool touchpadAxisChanged = false;public bool gripPressed = false;
public bool pointerPressed = false;public bool grabPressed = false;public bool usePressed = false;public bool menuPressed = false;
public event ControllerInteractionEventHandler TriggerPressed;public event ControllerInteractionEventHandler TriggerReleased;
public event ControllerInteractionEventHandler TriggerAxisChanged;
public event ControllerInteractionEventHandler ApplicationMenuPressed;public event ControllerInteractionEventHandler ApplicationMenuReleased;
public event ControllerInteractionEventHandler GripPressed;public event ControllerInteractionEventHandler GripReleased;
public event ControllerInteractionEventHandler TouchpadPressed;public event ControllerInteractionEventHandler TouchpadReleased;
public event ControllerInteractionEventHandler TouchpadTouchStart;public event ControllerInteractionEventHandler TouchpadTouchEnd;
public event ControllerInteractionEventHandler TouchpadAxisChanged;
public event ControllerInteractionEventHandler AliasPointerOn;public event ControllerInteractionEventHandler AliasPointerOff;
public event ControllerInteractionEventHandler AliasGrabOn;public event ControllerInteractionEventHandler AliasGrabOff;
public event ControllerInteractionEventHandler AliasUseOn;public event ControllerInteractionEventHandler AliasUseOff;
public event ControllerInteractionEventHandler AliasMenuOn;public event ControllerInteractionEventHandler AliasMenuOff;
private uint controllerIndex;private SteamVR_TrackedObject trackedController;private SteamVR_Controller.Device device;
private Vector2 touchpadAxis = Vector2.zero;private Vector2 triggerAxis = Vector2.zero;
public virtual void OnTriggerPressed(ControllerInteractionEventArgs e){if (TriggerPressed != null)TriggerPressed(this, e);}
public virtual void OnTriggerReleased(ControllerInteractionEventArgs e){if (TriggerReleased != null)TriggerReleased(this, e);}
public virtual void OnTriggerAxisChanged(ControllerInteractionEventArgs e){if (TriggerAxisChanged != null)TriggerAxisChanged(this, e);}
public virtual void OnApplicationMenuPressed(ControllerInteractionEventArgs e){if (ApplicationMenuPressed != null)ApplicationMenuPressed(this, e);}
public virtual void OnApplicationMenuReleased(ControllerInteractionEventArgs e){if (ApplicationMenuReleased != null)ApplicationMenuReleased(this, e);}
public virtual void OnGripPressed(ControllerInteractionEventArgs e){if (GripPressed != null)GripPressed(this, e);}
public virtual void OnGripReleased(ControllerInteractionEventArgs e){if (GripReleased != null)GripReleased(this, e);}
public virtual void OnTouchpadPressed(ControllerInteractionEventArgs e){if (TouchpadPressed != null)TouchpadPressed(this, e);}
public virtual void OnTouchpadReleased(ControllerInteractionEventArgs e){if (TouchpadReleased != null)TouchpadReleased(this, e);}
public virtual void OnTouchpadTouchStart(ControllerInteractionEventArgs e){if (TouchpadTouchStart != null)TouchpadTouchStart(this, e);}
public virtual void OnTouchpadTouchEnd(ControllerInteractionEventArgs e){if (TouchpadTouchEnd != null)TouchpadTouchEnd(this, e);}
public virtual void OnTouchpadAxisChanged(ControllerInteractionEventArgs e){if (TouchpadAxisChanged != null)TouchpadAxisChanged(this, e);}
public virtual void OnAliasPointerOn(ControllerInteractionEventArgs e){if (AliasPointerOn != null)AliasPointerOn(this, e);}
public virtual void OnAliasPointerOff(ControllerInteractionEventArgs e){if (AliasPointerOff != null)AliasPointerOff(this, e);}
public virtual void OnAliasGrabOn(ControllerInteractionEventArgs e){if (AliasGrabOn != null)AliasGrabOn(this, e);}
public virtual void OnAliasGrabOff(ControllerInteractionEventArgs e){if (AliasGrabOff != null)AliasGrabOff(this, e);}
public virtual void OnAliasUseOn(ControllerInteractionEventArgs e){if (AliasUseOn != null)AliasUseOn(this, e);}
public virtual void OnAliasUseOff(ControllerInteractionEventArgs e){if (AliasUseOff != null)AliasUseOff(this, e);}
public virtual void OnAliasMenuOn(ControllerInteractionEventArgs e){if (AliasMenuOn != null)AliasMenuOn(this, e);}
public virtual void OnAliasMenuOff(ControllerInteractionEventArgs e){if (AliasMenuOff != null)AliasMenuOff(this, e);}
ControllerInteractionEventArgs SetButtonEvent(ref bool buttonBool, bool value, float buttonPressure){buttonBool = value;ControllerInteractionEventArgs e;e.controllerIndex = controllerIndex;e.buttonPressure = buttonPressure;e.touchpadAxis = device.GetAxis();
float angle = Mathf.Atan2(e.touchpadAxis.y, e.touchpadAxis.x) * Mathf.Rad2Deg;angle = 90.0f - angle;if (angle < 0)angle += 360.0f;
e.touchpadAngle = angle;
return e;}
void Awake(){trackedController = GetComponent<SteamVR_TrackedObject>();}
void Start(){controllerIndex = (uint)trackedController.index;device = SteamVR_Controller.Input((int)controllerIndex);}
void EmitAlias(ButtonAlias type, bool touchDown, float buttonPressure, ref bool buttonBool){if (pointerToggleButton == type){if (touchDown){pointerPressed = true;OnAliasPointerOn(SetButtonEvent(ref buttonBool, true, buttonPressure));}else{pointerPressed = false;OnAliasPointerOff(SetButtonEvent(ref buttonBool, false, buttonPressure));}}
if (grabToggleButton == type){if (touchDown){grabPressed = true;OnAliasGrabOn(SetButtonEvent(ref buttonBool, true, buttonPressure));}else{grabPressed = false;OnAliasGrabOff(SetButtonEvent(ref buttonBool, false, buttonPressure));}}
if (useToggleButton == type){if (touchDown){usePressed = true;OnAliasUseOn(SetButtonEvent(ref buttonBool, true, buttonPressure));}else{usePressed = false;OnAliasUseOff(SetButtonEvent(ref buttonBool, false, buttonPressure));}}
if (menuToggleButton == type){if (touchDown){menuPressed = true;OnAliasMenuOn(SetButtonEvent(ref buttonBool, true, buttonPressure));}else{menuPressed = false;OnAliasMenuOff(SetButtonEvent(ref buttonBool, false, buttonPressure));}}}
bool Vector2ShallowEquals(Vector2 vectorA, Vector2 vectorB){return (vectorA.x.ToString("F" + axisFidelity) == vectorB.x.ToString("F" + axisFidelity) &&vectorA.y.ToString("F" + axisFidelity) == vectorB.y.ToString("F" + axisFidelity));}
void Update(){controllerIndex = (uint)trackedController.index;device = SteamVR_Controller.Input((int)controllerIndex);
Vector2 currentTriggerAxis = device.GetAxis(Valve.VR.EVRButtonId.k_EButton_SteamVR_Trigger);Vector2 currentTouchpadAxis = device.GetAxis();
if (Vector2ShallowEquals(triggerAxis, currentTriggerAxis)){triggerAxisChanged = false;}else{OnTriggerAxisChanged(SetButtonEvent(ref triggerAxisChanged, true, currentTriggerAxis.x));}
if (Vector2ShallowEquals(touchpadAxis, currentTouchpadAxis)){touchpadAxisChanged = false;}else{OnTouchpadAxisChanged(SetButtonEvent(ref touchpadTouched, true, 1f));touchpadAxisChanged = true;}
touchpadAxis = new Vector2(currentTouchpadAxis.x, currentTouchpadAxis.y);triggerAxis = new Vector2(currentTriggerAxis.x, currentTriggerAxis.y);
//Triggerif (device.GetTouchDown(SteamVR_Controller.ButtonMask.Trigger)){OnTriggerPressed(SetButtonEvent(ref triggerPressed, true, currentTriggerAxis.x));EmitAlias(ButtonAlias.Trigger, true, currentTriggerAxis.x, ref triggerPressed);}else if (device.GetTouchUp(SteamVR_Controller.ButtonMask.Trigger)){OnTriggerReleased(SetButtonEvent(ref triggerPressed, false, 0f));EmitAlias(ButtonAlias.Trigger, false, 0f, ref triggerPressed);}
//ApplicationMenuif (device.GetTouchDown(SteamVR_Controller.ButtonMask.ApplicationMenu)){OnApplicationMenuPressed(SetButtonEvent(ref applicationMenuPressed, true, 1f));EmitAlias(ButtonAlias.Application_Menu, true, 1f, ref applicationMenuPressed);}else if (device.GetTouchUp(SteamVR_Controller.ButtonMask.ApplicationMenu)){
OnApplicationMenuReleased(SetButtonEvent(ref applicationMenuPressed, false, 0f));EmitAlias(ButtonAlias.Application_Menu, false, 0f, ref applicationMenuPressed);}
//Gripif (device.GetTouchDown(SteamVR_Controller.ButtonMask.Grip)){OnGripPressed(SetButtonEvent(ref gripPressed, true, 1f));EmitAlias(ButtonAlias.Grip, true, 1f, ref gripPressed);}else if (device.GetTouchUp(SteamVR_Controller.ButtonMask.Grip)){OnGripReleased(SetButtonEvent(ref gripPressed, false, 0f));EmitAlias(ButtonAlias.Grip, false, 0f, ref gripPressed);}
//Touchpad Pressedif (device.GetPressDown(SteamVR_Controller.ButtonMask.Touchpad)){OnTouchpadPressed(SetButtonEvent(ref touchpadPressed, true, 1f));EmitAlias(ButtonAlias.Touchpad_Press, true, 1f, ref touchpadPressed);}else if (device.GetPressUp(SteamVR_Controller.ButtonMask.Touchpad)){OnTouchpadReleased(SetButtonEvent(ref touchpadPressed, false, 0f));EmitAlias(ButtonAlias.Touchpad_Press, false, 0f, ref touchpadPressed);}
//Touchpad Touchedif (device.GetTouchDown(SteamVR_Controller.ButtonMask.Touchpad)){OnTouchpadTouchStart(SetButtonEvent(ref touchpadTouched, true, 1f));EmitAlias(ButtonAlias.Touchpad_Touch, true, 1f, ref touchpadTouched);}else if (device.GetTouchUp(SteamVR_Controller.ButtonMask.Touchpad)){OnTouchpadTouchEnd(SetButtonEvent(ref touchpadTouched, false, 0f));EmitAlias(ButtonAlias.Touchpad_Touch, false, 0f, ref touchpadTouched);}}}}
在这个类中我们可以看到,枚举类ButtonAlias里的元素,分别对应HTCVive控制柄上的按钮:
- Trigger:能扣动的那个按钮;
- Grip:控制柄侧面的两个按钮;
- Touchpad_Touch:触摸板的触控;
- Touchpad_Press:触摸板的按压;
- Application_Menu:触摸板上方的小按钮,触摸板下方的按钮是统一的总菜单功能键,不能自定义开发,所以没有与它对应的名称。(这句是我的理解,但没有试验过,如果哪位试过,望告知...)
- TriggerPressed——扳机按下;
- TriggerReleased——扳机放开;
- TriggerAxisChanged——扳机轴值的改变;
- ApplicationMenuPressed——ApplicationMenu按钮按下;
- ApplicationMenuReleased——ApplicationMenu按钮放开;
- GripPressed——Grip按钮按下;
- GripReleased——Grip按钮放开;
- TouchpadPressed——触摸板按钮按下;
- TouchpadReleased——触摸板按钮放开;
- TouchpadTouchStart——触摸板开始触摸;
- TouchpadTouchEnd——触摸板结束触摸;
- TouchpadAxisChanged——触摸板触摸时的轴值变化;
-
- AliasPointerOn;
- AliasPointerOff;
- AliasGrabOn;
- AliasGrabOff;
- AliasUseOn;
- AliasUseOff;
- AliasMenuOn;
- AliasMenuOff;
接下来,我们来看下,Toolkit具体是怎么自己封装的SteamVR控制柄的事件的。
在VRTK_ControllerEvents.cs这个脚本的Update()中,我们先找到这样一段代码:
从这段代码中(代码的意思很简单,我就不说了,具体看下它的逻辑处理),我们可以看到,每帧监听一次SteamVR中的控制柄状态,当监听到状态后,执行一次自己的委托事件(ControllerInteractionEventHandler),并且执行一次EmitAlias(ButtonAlias type, bool touchDown, float buttonPressure, ref bool buttonBool)方法。EmitAlias方法是用来判断并分发事件给AliasXX(oN/oFF)的(就是刚才分割线以下的那些委托方法)。
至于AliasXX(oN/oFF)这些委托方法怎么用?我们可以先看下这个VRTK_ControllerEvents.cs挂接脚本在Inspector面板中的序列化属性:
AliasXX(oN/oFF)委托方法对应:
至于Inspector面板中的其它属性,都没有实际用处,不需要我们去自定义,所以如果不想看到的话,可以自己在脚本中的相应变量上加上[HideInInspector],最后:
Tollkit对SteamVR的封装逻辑很简单,用这么长的篇幅来说,主要目的还是想自己再仔细研究下,虽然它的逻辑简单,但是我觉的它的代码结构很漂亮。不感兴趣的,可以忽略。至于怎么用,Toolkit也给出了一个挂接脚本,很简单的用法,我挂一下它的代码就行,看完很容易理解: