XR Interaction Toolkit 2.5.2
Pico 设备按键、震动
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
using CommonUsages = UnityEngine.XR.CommonUsages;
using InputDevice = UnityEngine.XR.InputDevice;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.EventSystems;
public class InputEvent : MonoBehaviour
{
private InputDeviceEvent leftEvent;
private InputDeviceEvent rightEvent;
//public InputDeviceEvent headEvent;
[SerializeField]
private XRRayInteractor leftHandRay;
[SerializeField]
private XRRayInteractor rightHandRay;
void Awake()
{
leftEvent = new InputDeviceEvent(XRNode.LeftHand);
rightEvent = new InputDeviceEvent(XRNode.RightHand);
//headEvent = new InputDeviceEvent(XRNode.Head);
//Test();
}
private void FixedUpdate()
{
leftEvent.Update();
rightEvent.Update();
}
#region Core
/// <summary>
/// 清空事件
/// </summary>
/// <param name="id">0:双手 1:左手 2:右手</param>
public void RmoveActionAll(int id = 0)
{
switch (id)
{
case 0:
leftEvent.RmoveActionAll();
rightEvent.RmoveActionAll(); break;
case 1:
leftEvent.RmoveActionAll(); break;
case 2:
rightEvent.RmoveActionAll(); break;
default:
break;
}
}
/// <summary>
/// 添加 移除事件
/// </summary>
/// <param name="id">0:双手 1:左手 2:右手</param>
/// <param name="isAdd"></param>
/// <param name="usagesType"></param>
/// <param name="btnEnter"></param>
/// <param name="btnStay"></param>
/// <param name="btnExit"></param>
public void AddOrRmoveAction(int id, bool isAdd, CommonUsagesType usagesType, Action<Vector2> btnEnter = null, Action<Vector2> btnStay = null, Action<Vector2> btnExit = null)
{
switch (id)
{
case 0:
leftEvent.AddOrRmoveAction(isAdd, usagesType, btnEnter, btnStay, btnExit);
rightEvent.AddOrRmoveAction(isAdd, usagesType, btnEnter, btnStay, btnExit); break;
case 1:
leftEvent.AddOrRmoveAction(isAdd, usagesType, btnEnter, btnStay, btnExit); break;
case 2:
rightEvent.AddOrRmoveAction(isAdd, usagesType, btnEnter, btnStay, btnExit); break;
default:
break;
}
}
/// <summary>
/// 添加 移除事件
/// </summary>
/// <param name="id">0:双手 1:左手 2:右手</param>
/// <param name="isAdd"></param>
/// <param name="usagesType"></param>
/// <param name="btnEnter"></param>
/// <param name="btnStay"></param>
/// <param name="btnExit"></param>
public void AddOrRmoveAction(int id, bool isAdd, CommonUsagesType usagesType, Action btnEnter = null, Action btnStay = null, Action btnExit = null)
{
switch (id)
{
case 0:
leftEvent.AddOrRmoveAction(isAdd, usagesType, btnEnter, btnStay, btnExit);
rightEvent.AddOrRmoveAction(isAdd, usagesType, btnEnter, btnStay, btnExit); break;
case 1:
leftEvent.AddOrRmoveAction(isAdd, usagesType, btnEnter, btnStay, btnExit); break;
case 2:
rightEvent.AddOrRmoveAction(isAdd, usagesType, btnEnter, btnStay, btnExit); break;
default:
break;
}
}
/// <summary>
/// 震动
/// </summary>
/// <param name="id">0:双手 1:左手 2:右手</param>
/// <param name="amplitude">振幅 0 - 1</param>
/// <param name="duration">时长 秒</param>
public void HapticAction(int id, float amplitude = 0.5f, float duration = 1) // 秒
{
switch (id)
{
case 0:
leftEvent.Haptic(amplitude, duration);
rightEvent.Haptic(amplitude, duration); break;
case 1:
leftEvent.Haptic(amplitude, duration); break;
case 2:
rightEvent.Haptic(amplitude, duration); break;
default:
break;
}
}
#endregion
#region 扩展
/// <summary>
/// 添加事件
/// </summary>
/// <param name="id">0:双手 1:左手 2:右手</param>
/// <param name="usagesType"></param>
/// <param name="btnEnter"></param>
/// <param name="btnStay"></param>
/// <param name="btnExit"></param>
public void AddAction(int id, CommonUsagesType usagesType, Action btnEnter = null, Action btnStay = null, Action btnExit = null)
{
AddOrRmoveAction(id, true, usagesType, btnEnter, btnStay, btnExit);
}
/// <summary>
/// 添加事件
/// </summary>
/// <param name="id">0:双手 1:左手 2:右手</param>
/// <param name="usagesType"></param>
/// <param name="btnEnter"></param>
/// <param name="btnStay"></param>
/// <param name="btnExit"></param>
public void AddAction(int id, CommonUsagesType usagesType, Action<Vector2> btnEnter = null, Action<Vector2> btnStay = null, Action<Vector2> btnExit = null)
{
AddOrRmoveAction(id, true, usagesType, btnEnter, btnStay, btnExit);
}
/// <summary>
/// 移除事件
/// </summary>
/// <param name="id">0:双手 1:左手 2:右手</param>
/// <param name="usagesType"></param>
/// <param name="btnEnter"></param>
/// <param name="btnStay"></param>
/// <param name="btnExit"></param>
public void RmoveAction(int id, CommonUsagesType usagesType, Action btnEnter = null, Action btnStay = null, Action btnExit = null)
{
AddOrRmoveAction(id, false, usagesType, btnEnter, btnStay, btnExit);
}
/// <summary>
/// 移除事件
/// </summary>
/// <param name="id">0:双手 1:左手 2:右手</param>
/// <param name="usagesType"></param>
/// <param name="btnEnter"></param>
/// <param name="btnStay"></param>
/// <param name="btnExit"></param>
public void RmoveAction(int id, CommonUsagesType usagesType, Action<Vector2> btnEnter = null, Action<Vector2> btnStay = null, Action<Vector2> btnExit = null)
{
AddOrRmoveAction(id, false, usagesType, btnEnter, btnStay, btnExit);
}
#endregion
#region 测试
void Test()
{
AddAction(2, CommonUsagesType.AXButton, null, TestActionStay, TestActionExit);
AddAction(2, CommonUsagesType.AXButton, TestActionEnter2, TestActionStay2, null);
AddAction(2, CommonUsagesType.Joy2DAxisMove, TestJoyActionEnter, TestJoyActionStay, TestJoyActionExit);
Invoke("Test2", 50);
}
void Test2()
{
Debug.Log("Clear");
RmoveAction(2, CommonUsagesType.AXButton, null, TestActionStay, TestActionExit);
RmoveAction(2, CommonUsagesType.AXButton, TestActionEnter2, TestActionStay2, null);
RmoveAction(2, CommonUsagesType.Joy2DAxisMove, TestJoyActionEnter, TestJoyActionStay, TestJoyActionExit);
}
void TestActionEnter2()
{
Debug.Log("TestActionEnter2:");
}
void TestActionStay2()
{
Debug.Log("TestActionStay2:");
}
void TestActionExit2()
{
Debug.Log("TestActionExit2:");
}
void TestActionEnter()
{
Debug.Log("TestActionEnter:");
}
void TestActionStay()
{
Debug.Log("TestActionStay:");
}
void TestActionExit()
{
Debug.Log("TestActionExit:");
}
void TestJoyActionEnter(Vector2 aix)
{
Debug.Log("TestJoyActionEnter:" + aix);
}
void TestJoyActionStay(Vector2 aix)
{
Debug.Log("TestJoyActionStay:" + aix);
}
void TestJoyActionExit(Vector2 aix)
{
Debug.Log("TestJoyActionExit:" + aix);
}
#endregion
#region XRRayInteractor
public RaycastResult GetUIRaycast(int id = 0)
{
RaycastResult raycastResult;
switch (id)
{
case 1: leftHandRay.TryGetCurrentUIRaycastResult(out raycastResult); break;
case 2: rightHandRay.TryGetCurrentUIRaycastResult(out raycastResult); break;
default:
leftHandRay.TryGetCurrentUIRaycastResult(out raycastResult);
if (raycastResult.gameObject != null)
{
}
else
{
rightHandRay.TryGetCurrentUIRaycastResult(out raycastResult);
if (raycastResult.gameObject != null)
{
}
}
break;
}
return raycastResult;
}
public Transform GetRaycast(int id = 0)
{
switch (id)
{
case 1: return leftHandRay.rayEndTransform; ;
case 2: return rightHandRay.rayEndTransform;
default:
if (leftHandRay.rayEndTransform != null)
{
return leftHandRay.rayEndTransform;
}
else if (rightHandRay.rayEndTransform != null)
{
return rightHandRay.rayEndTransform;
}
break;
}
return null;
}
public bool GetRaycast(out Vector3 pos, int mask, int id = 0)
{
bool state = false;
switch (id)
{
case 1: state = GetRaycast(leftHandRay.transform, out pos, mask); break;
case 2: state = GetRaycast(rightHandRay.transform, out pos, mask); break;
default:
state = GetRaycast(leftHandRay.transform, out pos, mask);
if (!state)
state = GetRaycast(rightHandRay.transform, out pos, mask);
break;
}
return state;
}
private bool GetRaycast(Transform rayTrans, out Vector3 pos, int mask)
{
pos = Vector3.zero;
if (Physics.Raycast(rayTrans.transform.position, rayTrans.transform.forward, out RaycastHit hit, 1000, mask))
{
pos = hit.point;
return true;
}
return false;
}
#endregion
}
[Serializable]
public class InputDeviceEvent
{
#region public event
public Action onTriggerEnter;
public Action onTriggerStay;
public Action onTriggerExit;
public Action onGripEnter;
public Action onGripStay;
public Action onGripUp;
public Action onAXButtonEnter;
public Action onAXButtonStay;
public Action onAXButtonExit;
public Action onBYButtonEnter;
public Action onBYButtonStay;
public Action onBYButonExit;
public Action onMenuButtonEnter;
public Action onMenuButtonStay;
public Action onMenuButtonExit;
public Action onJoy2DAxisClickEnter;
public Action onJoy2DAxisClickStay;
public Action onJoy2DAxisClickExit;
public Action<Vector2> onJoy2DAxisMoveEnter;
public Action<Vector2> onJoy2DAxisMoveExit;
public Action<Vector2> onJoy2DAxisMoveStay;
#endregion
//提供状态字典独立记录各个feature的状态
private Dictionary<string, bool> stateDic = new Dictionary<string, bool>();
private XRNode xRNode;
private InputDevice inputDevice;
public InputDeviceEvent()
{
}
public InputDeviceEvent(XRNode xRNode)
{
this.xRNode = xRNode;
Init();
}
private void Init()
{
if (inputDevice == null || !inputDevice.isValid)
{
inputDevice = InputDevices.GetDeviceAtXRNode(xRNode);
}
}
public void Update() //******************每帧轮询监听事件***********************
{
if (inputDevice != null && inputDevice.isValid)
{
ButtonDispatchModel(inputDevice, CommonUsages.triggerButton, onTriggerEnter, onTriggerStay, onTriggerExit);//扳机
ButtonDispatchModel(inputDevice, CommonUsages.gripButton, onGripEnter, onGripStay, onGripUp);//抓取
ButtonDispatchModel(inputDevice, CommonUsages.primaryButton, onAXButtonEnter, onAXButtonStay, onAXButtonExit);//AX
ButtonDispatchModel(inputDevice, CommonUsages.secondaryButton, onBYButtonEnter, onBYButtonStay, onBYButonExit);//BY
ButtonDispatchModel(inputDevice, CommonUsages.menuButton, onMenuButtonEnter, onMenuButtonStay, onMenuButtonExit); //菜单 pico右手柄 无效
ButtonDispatchModel(inputDevice, CommonUsages.primary2DAxisClick, onJoy2DAxisClickEnter, onJoy2DAxisClickStay, onJoy2DAxisClickExit);//摇杆
JoyStickDispatchModel(inputDevice, CommonUsages.primary2DAxis, onJoy2DAxisMoveEnter, onJoy2DAxisMoveStay, onJoy2DAxisMoveExit);//摇杆
}
else
{
Init();
}
}
/// <summary>
/// 手柄振动
/// </summary>
/// <param name="amplitude">振幅为 0 - 1</param>
/// <param name="duration">振动时长为 秒 </param>
public void Haptic(float amplitude = 0.5f, float duration = 1) // 秒
{
if (inputDevice != null && inputDevice.isValid)
{
bool isTry = inputDevice.TryGetHapticCapabilities(out HapticCapabilities capabilities);
if (isTry)
{
//int channel = 0;
//uint channel = capabilities.numChannels;
//int channel = xRNode == XRNode.LeftHand ? 0 : 1;
//震动方法,第一个参数为频道,默认0即可,第二个参数是震动频率0-1,第三个参数为震动时长
//inputDevice.SendHapticImpulse((uint)channel, amplitude, duration);
////震动强度:0.1f、时间2000毫秒(2秒)、震动左手柄还是右手柄(这里是两个都震动)
//Unity.XR.PXR.PXR_Input.SetControllerVibration(amplitude, (int)duration * 1000, xRNode == XRNode.LeftHand ? Unity.XR.PXR.PXR_Input.Controller.LeftController : Unity.XR.PXR.PXR_Input.Controller.RightController);
//编辑器 无反应 发布有效
//为右手柄开启触觉反馈,振幅为 0.5,振动时长为 500ms,振动频率为 100Hz
Unity.XR.PXR.PXR_Input.SendHapticImpulse(
xRNode == XRNode.LeftHand ? Unity.XR.PXR.PXR_Input.VibrateType.LeftController : Unity.XR.PXR.PXR_Input.VibrateType.RightController
, amplitude, (int)(duration * 1000), 100);
Log("Haptic: " + inputDevice.name);
}
}
else
{
Init();
}
}
/// <summary>
/// 按钮事件源触发模板
/// </summary>
/// <param name="device">设备</param>
/// <param name="usage">功能特征</param>
/// <param name="btnEnter">开始事件</param>
/// <param name="btnStay">进行中事件</param>
/// <param name="btnExit">结束事件</param>
private void ButtonDispatchModel(InputDevice device, InputFeatureUsage<bool> usage, Action btnEnter, Action btnStay, Action btnExit)
{
//为首次执行的feature添加bool状态 -- 用以判断Enter和Up状态
string featureKey = $"{device.characteristics}_{usage.name}";
if (!stateDic.ContainsKey(featureKey))
{
stateDic.Add(featureKey, false);
}
bool isDown;
if (device.TryGetFeatureValue(usage, out isDown) && isDown)
{
if (!stateDic[featureKey])
{
stateDic[featureKey] = true;
btnEnter?.Invoke();
Haptic(0.5f, 0.01f);
Log($"usage Enter: {featureKey} {btnEnter}");
}
btnStay?.Invoke();
Log($"usage Stay: {featureKey} {btnStay}");
}
else
{
if (stateDic[featureKey])
{
btnExit?.Invoke();
stateDic[featureKey] = false;
Log($"usage Exit: {featureKey} {btnExit}");
}
}
}
/// <summary>
/// 摇杆事件源触发模板
/// </summary>
/// <param name="device">设备</param>
/// <param name="usage">功能特征</param>
/// <param name="btnEnter">开始事件</param>
/// <param name="btnStay">进行中事件</param>
/// <param name="btnExit">结束事件</param>
private void JoyStickDispatchModel(InputDevice device, InputFeatureUsage<Vector2> usage, Action<Vector2> btnEnter, Action<Vector2> btnStay, Action<Vector2> btnExit)
{
//为首次执行的feature添加bool状态 -- 用以判断Enter和Up状态
string featureKey = $"{device.characteristics}_{usage.name}";
if (!stateDic.ContainsKey(featureKey))
{
stateDic.Add(featureKey, false);
}
Vector2 axis;
bool isDown = device.TryGetFeatureValue(usage, out axis);
if (isDown && !axis.Equals(Vector2.zero))
{
if (!stateDic[featureKey])
{
stateDic[featureKey] = true;
btnEnter?.Invoke(axis);
Log($"usage Enter: {featureKey} {btnEnter} {axis}");
}
btnStay?.Invoke(axis);
Log($"usage Stay: {featureKey} {btnStay} {axis}");
}
else
{
if (stateDic[featureKey])
{
btnExit?.Invoke(axis);
stateDic[featureKey] = false;
Log($"usage Exit: {featureKey} {btnExit} {axis}");
}
}
}
private void Log(string value)
{
Debug.Log(value);
DebugHelper.instance.Log(value);
}
#region 扩展
public void RmoveActionAll()
{
string[] enumNames = Enum.GetNames(typeof(CommonUsagesType));
int count = enumNames.Length;
for (int i = 0; i < count; i++)
{
RmoveAction((CommonUsagesType)Enum.Parse(typeof(CommonUsagesType), enumNames[i]));
}
//foreach (CommonUsagesType item in Enum.GetValues(typeof(CommonUsagesType)))
//{
// RmoveAction(item);
//}
}
public void RmoveAction(CommonUsagesType usagesType)
{
switch (usagesType)
{
case CommonUsagesType.TriggerButton:
onTriggerEnter = null;
onTriggerStay = null;
onTriggerExit = null;
break;
case CommonUsagesType.GripButton:
onGripEnter = null;
onGripStay = null;
onGripUp = null;
break;
case CommonUsagesType.AXButton:
onAXButtonEnter = null;
onAXButtonStay = null;
onAXButtonExit = null;
break;
case CommonUsagesType.BYButton:
onBYButtonEnter = null;
onBYButtonStay = null;
onBYButonExit = null;
break;
case CommonUsagesType.MenuButton:
onMenuButtonEnter = null;
onMenuButtonStay = null;
onMenuButtonExit = null;
break;
case CommonUsagesType.Joy2DAxisButton:
onJoy2DAxisClickEnter = null;
onJoy2DAxisClickStay = null;
onJoy2DAxisClickExit = null;
break;
case CommonUsagesType.Joy2DAxisMove:
onJoy2DAxisMoveEnter = null;
onJoy2DAxisMoveStay = null;
onJoy2DAxisMoveExit = null;
break;
default:
break;
}
}
public void AddOrRmoveAction(bool isAdd, CommonUsagesType usagesType, Action btnEnter = null, Action btnStay = null, Action btnExit = null)
{
switch (usagesType)
{
case CommonUsagesType.TriggerButton:
AddOrRmoveAction(isAdd, ref onTriggerEnter, ref onTriggerStay, ref onTriggerExit, btnEnter, btnStay, btnExit);
break;
case CommonUsagesType.GripButton:
AddOrRmoveAction(isAdd, ref onGripEnter, ref onGripStay, ref onGripUp, btnEnter, btnStay, btnExit);
break;
case CommonUsagesType.AXButton:
AddOrRmoveAction(isAdd, ref onAXButtonEnter, ref onAXButtonStay, ref onAXButtonExit, btnEnter, btnStay, btnExit);
break;
case CommonUsagesType.BYButton:
AddOrRmoveAction(isAdd, ref onBYButtonEnter, ref onBYButtonStay, ref onBYButonExit, btnEnter, btnStay, btnExit);
break;
case CommonUsagesType.MenuButton:
AddOrRmoveAction(isAdd, ref onMenuButtonEnter, ref onMenuButtonStay, ref onMenuButtonExit, btnEnter, btnStay, btnExit);
break;
case CommonUsagesType.Joy2DAxisButton:
AddOrRmoveAction(isAdd, ref onJoy2DAxisClickEnter, ref onJoy2DAxisClickStay, ref onJoy2DAxisClickExit, btnEnter, btnStay, btnExit);
break;
default:
break;
}
}
public void AddOrRmoveAction(bool isAdd, CommonUsagesType usagesType, Action<Vector2> btnEnter = null, Action<Vector2> btnStay = null, Action<Vector2> btnExit = null)
{
switch (usagesType)
{
case CommonUsagesType.Joy2DAxisMove:
AddOrRmoveAction(isAdd, ref onJoy2DAxisMoveEnter, ref onJoy2DAxisMoveStay, ref onJoy2DAxisMoveExit, btnEnter, btnStay, btnExit);
break;
default:
break;
}
}
private void AddOrRmoveAction(bool isAdd, ref Action obtnEnter, ref Action obtnStay, ref Action obtnExit, Action btnEnter, Action btnStay, Action btnExit)
{
if (isAdd)
{
obtnEnter += btnEnter;
obtnStay += btnStay;
obtnExit += btnExit;
}
else
{
obtnEnter -= btnEnter;
obtnStay -= btnStay;
obtnExit -= btnExit;
}
}
private void AddOrRmoveAction(bool isAdd, ref Action<Vector2> obtnEnter, ref Action<Vector2> obtnStay, ref Action<Vector2> obtnExit, Action<Vector2> btnEnter, Action<Vector2> btnStay, Action<Vector2> btnExit)
{
if (isAdd)
{
obtnEnter += btnEnter;
obtnStay += btnStay;
obtnExit += btnExit;
}
else
{
obtnEnter -= btnEnter;
obtnStay -= btnStay;
obtnExit -= btnExit;
}
}
#endregion
}
public enum CommonUsagesType
{
TriggerButton,
GripButton,
AXButton,
BYButton,
MenuButton,
Joy2DAxisButton,
Joy2DAxisMove,
}