Controller Events 腳本用于統(tǒng)一處理Vive手柄發(fā)送的事件.
Controller Events 腳本掛載在[CameraRig] 預(yù)置件下的Controller 子對(duì)象上抓半,為手柄上每個(gè)按鈕的觸發(fā)提供事件監(jiān)聽(tīng) (包括menu按鈕).
當(dāng)手柄上某個(gè)按鈕被按下, 腳本會(huì)發(fā)送一個(gè)事件负蚊, 通知其他腳本這個(gè)點(diǎn)擊事件眨层,而具體的點(diǎn)擊邏輯并不需要寫在Controller Events腳本里面. 當(dāng)一個(gè)按鈕被釋放, 腳本同樣會(huì)發(fā)送一個(gè)事件通知其他腳本這個(gè)按鈕被釋放了.
這個(gè)腳本也有各個(gè)按鈕相應(yīng)的public boolean屬性以便其他腳本獲取到所需按鈕的當(dāng)前狀態(tài),例如是否釋放,是否點(diǎn)擊等.
Inspector可見(jiàn)參數(shù)
unity手柄事件(VRTK_ControllerEvents)前鹅,在unity中与涡,可以通過(guò)下拉列表,指定這些行為的對(duì)應(yīng)按鈕是什么魏蔗?
· Pointer Toggle Button: 這個(gè)按鈕用于控制一束激光指示線開(kāi)/關(guān).
· Pointer Set Button: 這個(gè)按鈕用于設(shè)置指示線的目標(biāo)標(biāo)記.
· Grab Toggle Button: 這個(gè)按鈕用于控制抓取游戲中的物體.
· Use Toggle Button: 這個(gè)按鈕用于使用游戲中的物體.
· UI Click Button: 這個(gè)按鈕用于點(diǎn)擊UI元素.
· Menu Toggle Button:這個(gè)按鈕用于點(diǎn)擊彈出游戲內(nèi)置按鈕.
· Axis Fidelity: 坐標(biāo)變化的精度, 默認(rèn)為1. 大于2的數(shù)字將會(huì)導(dǎo)致過(guò)于靈敏的結(jié)果.
變量
· public bool triggerPressed - 當(dāng)trigger被扣下一半左右時(shí)為真.
· public bool triggerTouched - 當(dāng)trigger被扣下一點(diǎn)點(diǎn)時(shí)為真.
· public bool triggerHairlinePressed - 當(dāng)trigger比任何之前扣下的程度多時(shí)為真.
· public bool triggerClicked - 當(dāng)trigger完全扣下時(shí)為真.
· public bool triggerAxisChanged - 當(dāng)trigger位置改變時(shí)為真.
· public bool applicationMenuPressed - 當(dāng)application menu被按下時(shí)為真.
· public bool touchpadPressed - 當(dāng)touchpad被按下時(shí)為真.
· public bool touchpadTouched - 當(dāng)touchpad被觸碰時(shí)為真.
· public bool touchpadAxisChanged - 當(dāng)touchpad觸碰位置改變時(shí)為真.
· public bool gripPressed - 當(dāng)grip被按下時(shí)為真.
· public bool pointerPressed - 當(dāng)別名為pointer的按鈕被按下時(shí)為真.
· public bool grabPressed - 當(dāng)別名為grab的按鈕被按下時(shí)為真.
· public bool usePressed - 當(dāng)別名為use的按鈕被按下時(shí)為真.
· public bool uiClickPressed - 當(dāng)別名為UI click的按鈕被按下時(shí)為真.
· public bool menuPressed - 當(dāng)別名為menu的按鈕被按下時(shí)為真.
事件
· TriggerPressed - 當(dāng)trigger被扣下一半左右時(shí)發(fā)送事件.
· TriggerReleased - 當(dāng)Trigger從扣下一半的狀態(tài)釋放后發(fā)送事件.
· TriggerTouchStart - 當(dāng)trigger被扣下一點(diǎn)點(diǎn)時(shí)發(fā)送事件.
· TriggerTouchEnd - 當(dāng)trigger完全沒(méi)有被扣下時(shí)發(fā)送事件.
· TriggerHairlineStart - 當(dāng)trigger扣下的程度超過(guò)了當(dāng)前的hairline閾值時(shí)發(fā)送事件.
· TriggerHairlineEnd - 當(dāng)tringger釋放程度超過(guò)了當(dāng)前的hairline閾值時(shí)發(fā)送事件.
· TriggerClicked - 當(dāng)trigger在clicked之前扣下的過(guò)程中發(fā)送事件.
· TriggerUnclicked - 當(dāng)trigger不再一直處于clicked狀態(tài)時(shí)發(fā)送事件.
· TriggerAxisChanged - 當(dāng)trigger扣下的量發(fā)生變化時(shí)發(fā)送事件.
· ApplicationMenuPressed - 當(dāng)application menu被按下時(shí)發(fā)送事件.
· ApplicationMenuReleased - 當(dāng)application menu被釋放時(shí)發(fā)送事件.
· GripPressed - 當(dāng)grip被按下時(shí)發(fā)送事件.
· GripReleased - 當(dāng)grip被釋放時(shí)發(fā)送事件.
· TouchpadPressed - 當(dāng)touchpad被按下的時(shí)候發(fā)送事件(比觸摸的按壓程度大).
· TouchpadReleased - 當(dāng)touchpad從被按下(非觸碰)的狀態(tài)下釋放時(shí)發(fā)送事件.
· TouchpadTouchStart - 當(dāng)touchpad被觸摸時(shí)發(fā)送事件 (不是點(diǎn)擊或者摁下).
· TouchpadTouchEnd - 當(dāng)touchpad不再被觸摸時(shí)發(fā)送事件.
· TouchpadAxisChanged - 當(dāng)touchpad被觸摸的點(diǎn)改變時(shí)發(fā)送事件.
· AliasPointerOn - 當(dāng)pointer toggle(別名)被按下的時(shí)候發(fā)送事件.
· AliasPointerOff - 當(dāng)pointer toggle(別名)被釋放的時(shí)候發(fā)送事件.
· AliasPointerSet - 當(dāng)pointer set(別名)被釋放時(shí)發(fā)送事件.
· AliasGrabOn - 當(dāng)grab toggle(別名)被按下的時(shí)候發(fā)送事件.
· AliasGrabOff - 當(dāng)grab toggle(別名)被釋放的時(shí)候發(fā)送事件.
· AliasUseOn - 當(dāng)use toggle(別名)被按下的時(shí)候發(fā)送事件.
· AliasUseOff - 當(dāng)use toggle(別名)被釋放時(shí)發(fā)送事件.
· AliasMenuOn - 當(dāng)menu toggle(別名)被按下時(shí)發(fā)送事件.
· AliasMenuOff - 當(dāng)menu toggle(別名)被釋放時(shí)發(fā)送事件.
· AliasUIClickOn - 當(dāng)UI click(別名)被按下時(shí)發(fā)送事件.
· AliasUIClickOff - 當(dāng)UI click(別名)被釋放時(shí)發(fā)送事件.
事件和bool狀態(tài)變量有著對(duì)應(yīng)的關(guān)系砍的,通常一個(gè)bool狀態(tài)變量會(huì)對(duì)應(yīng)至少兩個(gè)按鈕事件
事件裝載參數(shù)
public struct ControllerInteractionEventArgs
{
public uint controllerIndex;
public float buttonPressure;
public Vector2 touchpadAxis;
public float touchpadAngle;
}
· uint controllerIndex - 當(dāng)前使用設(shè)備的索引.
· float buttonPressure - 按鈕的按壓數(shù)值. 0f 到 1f.
· Vector2 touchpadAxis - touchpad被觸摸的坐標(biāo). (0,0) 到 (1,1).
· float touchpadAngle - touchpad觸摸時(shí)滑動(dòng)的角度, top為0, bottom為180,以此類推其他 . 0f 到 360f.
委托類型
public delegate void ControllerInteractionEventHandler(object sender, ControllerInteractionEventArgs e);
聲明一個(gè)委托類型莺治,參數(shù)為object和ControllerInteractionEventArgs,綁定事件時(shí)一定要傳入這兩個(gè)參數(shù)廓鞠,按鈕被按下時(shí)會(huì)通過(guò)SetButtonEvent()方法來(lái)給ControllerInteractionEventArgs e分配值.
按鈕別名
public enum ButtonAlias
{
Trigger_Hairline,
Trigger_Touch,
Trigger_Press,
Trigger_Click,
Grip,
Touchpad_Touch,
Touchpad_Press,
Application_Menu,
Undefined
}
這個(gè)工具類給Vive手柄一些常用的操作取一些別名,和實(shí)際的按鈕建立映射谣旁,例如:
public ButtonAlias menuToggleButton = ButtonAlias.Application_Menu;
這個(gè)menuToggleButton與SteamVR中的
SteamVR_Controller.ButtonMask.ApplicationMenu
對(duì)應(yīng)床佳,當(dāng)這個(gè)按鈕被按下時(shí),別名按鈕對(duì)應(yīng)的事件(如果有綁定)也會(huì)發(fā)送
和SteamVR相關(guān)的全局變量
private uint controllerIndex;
private SteamVR_TrackedObject trackedController;
private SteamVR_Controller.Device device;
private Vector2 touchpadAxis = Vector2.zero;
private Vector2 triggerAxis = Vector2.zero;
private float hairTriggerDelta;
private Vector3 controllerVelocity = Vector3.zero;
private Vector3 controllerAngularVelocity = Vector3.zero;
· controllerIndex - 手柄的索引值榄审,通過(guò)trackedController.index獲取
· trackedController - gameobject綁定的SteamVR_TrackedObject腳本
· device - 設(shè)備類砌们,通過(guò)此類獲取實(shí)際中手柄的各種數(shù)據(jù)
· touchpadAxis - 全局變量,touchpad的坐標(biāo)
· triggerAxis - 全局變量瘟判,trigger的坐標(biāo)
· hairTriggerDelta -
· controllerVelocity - 手柄運(yùn)動(dòng)的速度
· controllerAngularVelocity - 手柄旋轉(zhuǎn)的角速度
事件發(fā)送方法
以O(shè)nTriggerPressed方法為例怨绣,其他都和這個(gè)差不多
public virtual void OnTriggerPressed(ControllerInteractionEventArgs e)
{
if (TriggerPressed != null)
{
TriggerPressed(this, e);//發(fā)送事件,通知綁定此事件的腳本拷获,執(zhí)行具體的邏輯篮撑,但是此處是真正最后調(diào)用的地方
}
}
裝載參數(shù)
private ControllerInteractionEventArgs SetButtonEvent(ref bool buttonBool, bool value, float buttonPressure)
{
buttonBool = value;
ControllerInteractionEventArgs e;
e.controllerIndex = controllerIndex;
e.buttonPressure = buttonPressure;
e.touchpadAxis = device.GetAxis();//調(diào)用SteamVR API獲取當(dāng)前的touchpad二維坐標(biāo)
e.touchpadAngle = CalculateTouchpadAxisAngle(e.touchpadAxis);//計(jì)算二維坐標(biāo)在圓形表盤上對(duì)應(yīng)的角度
return e;
}
通過(guò)傳入ref bool buttonBool,可以在對(duì)ControllerInteractionEventArgs進(jìn)行裝填的同時(shí)匆瓜,把事件對(duì)應(yīng)的按鈕bool狀態(tài)進(jìn)行更新赢笨,
例如TriggerPressed和TriggerReleased事件對(duì)應(yīng)的按鈕bool狀態(tài)是triggerPressed,當(dāng)發(fā)送TriggerPressed事件時(shí)要同時(shí)更新triggerPressed為true驮吱;發(fā)送TriggerReleased事件時(shí)要同時(shí)更新triggerPressed為false
初始化
private void Awake()
{
trackedController = GetComponent();
gameObject.layer = LayerMask.NameToLayer("Ignore Raycast");
}
private void Start()
{
//獲取當(dāng)前腳本attach的Controller的index
controllerIndex = (uint)trackedController.index;
if (controllerIndex < uint.MaxValue)
{
//獲取設(shè)備
device = SteamVR_Controller.Input((int)controllerIndex);
}
}
一般頭顯對(duì)應(yīng)的index為0茧妒,兩個(gè)手柄分別為0和1
別名按鈕事件發(fā)送
private 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));
}
}
…
根據(jù)type判斷是哪個(gè)別名按鈕,最后一個(gè)參數(shù)buttonBool對(duì)應(yīng)的是非別名的按鈕bool狀態(tài)左冬,例如這個(gè)pointerToggleButton桐筏,發(fā)送事件時(shí)要把touchpadPressed狀態(tài)更新,而更新為true還是false要根據(jù)touchDown的值來(lái)判斷拇砰,上面的OnAliasPointerOn等方法和OnTriggerPressed
值得注意的是梅忌,不同的別名對(duì)應(yīng)的可能是相同的按鈕狰腌,例如pointerToggleButton和pointerSetButton都是ButtonAlias.Touchpad_Press.
禁用事件
private void OnDisable()
{
//在0.1s內(nèi)調(diào)用DisableEvents(),禁用所有事件發(fā)送
Invoke("DisableEvents", 0.1f);
}
///
/// 禁用牧氮,還原琼腔,但是保存touchpad和trigger的坐標(biāo)等
///
private void DisableEvents()
{
if (triggerPressed)
{
OnTriggerReleased(SetButtonEvent(ref triggerPressed, false, 0f));
EmitAlias(ButtonAlias.Trigger_Press, false, 0f, ref triggerPressed);
}
...
triggerAxisChanged = false;
touchpadAxisChanged = false;
controllerIndex = (uint)trackedController.index;
if (controllerIndex < uint.MaxValue)
{
device = SteamVR_Controller.Input((int)controllerIndex);
Vector2 currentTriggerAxis = device.GetAxis(Valve.VR.EVRButtonId.k_EButton_SteamVR_Trigger);
Vector2 currentTouchpadAxis = device.GetAxis();
// 保存當(dāng)前的touchpad和trigger的設(shè)置.
touchpadAxis = new Vector2(currentTouchpadAxis.x, currentTouchpadAxis.y);
triggerAxis = new Vector2(currentTriggerAxis.x, currentTriggerAxis.y);
hairTriggerDelta = device.hairTriggerDelta;
}
}
這個(gè)方法應(yīng)該就是將所有事件對(duì)應(yīng)的按鈕bool狀態(tài)置為false,同時(shí)保存touchpad和trigger上的坐標(biāo)信息踱葛,但是為什么要重新獲取一次device呢丹莲?
Update()方法
private void Update()
{
controllerIndex = (uint)trackedController.index;
//Only continue if the controller index has been set to a sensible number
//SteamVR 在未找到Controller時(shí)會(huì)把index置為uint最大的值
if (controllerIndex >= uint.MaxValue)
{
return;
}
device = SteamVR_Controller.Input((int)controllerIndex);
Vector2 currentTriggerAxis = device.GetAxis(Valve.VR.EVRButtonId.k_EButton_SteamVR_Trigger);
Vector2 currentTouchpadAxis = device.GetAxis();
//Trigger Pressed
if (device.GetPressDown(SteamVR_Controller.ButtonMask.Trigger))
{
//發(fā)送事件,設(shè)triggerPressed為true,同時(shí)發(fā)送Trigger_Press對(duì)應(yīng)的別名按鈕事件
OnTriggerPressed(SetButtonEvent(ref triggerPressed, true, currentTriggerAxis.x));
EmitAlias(ButtonAlias.Trigger_Press, true, currentTriggerAxis.x, ref triggerPressed);
}
else if (device.GetPressUp(SteamVR_Controller.ButtonMask.Trigger))
{
OnTriggerReleased(SetButtonEvent(ref triggerPressed, false, 0f));
EmitAlias(ButtonAlias.Trigger_Press, false, 0f, ref triggerPressed);
}
...
// 保存當(dāng)前trigger和touchpad狀態(tài).
touchpadAxis = new Vector2(currentTouchpadAxis.x, currentTouchpadAxis.y);
triggerAxis = new Vector2(currentTriggerAxis.x, currentTriggerAxis.y);
hairTriggerDelta = device.hairTriggerDelta;
}
使用實(shí)例
例如在VRTK_ControllerEvents_ListenerExample中
GetComponent().TriggerPressed += new ControllerInteractionEventHandler(DoTriggerPressed);
private void DoTriggerPressed(object sender, ControllerInteractionEventArgs e)
{
DebugLogger(e.controllerIndex, "TRIGGER", "pressed", e);
}
獲取到當(dāng)前Controller綁定的VRTK_ControllerEvents腳本尸诽,為它的TriggerPressed綁定DoTriggerPressed方法甥材,在VRTK_ControllerEvents腳本中,每一幀會(huì)檢測(cè)trigger是否被按下逊谋,如果按下擂达,則發(fā)送事件
OnTriggerPressed(SetButtonEvent(ref triggerPressed, true, currentTriggerAxis.x));
然后在OnTriggerPressed方法里執(zhí)行TriggerPressed(this, e);
此時(shí)DoTriggerPressed(this,e)被真正調(diào)用,而example腳本中無(wú)需在update中寫代碼胶滋,只需要在初始化的時(shí)候綁定事件就可以了.