SteamVR 插件交互解析
1动作和动作集
动作集->动作:SteamVR2.x的核心
SteamVR动作类型:
- Boolearn:布尔型,通常用于按钮动作
- Sigle:类似浮点,返回0~1浮点数,常用于获取Trigger键的键程值。
- vector2:二维,如获取Trackpad上手指接触点坐标
- vector2:三维,入获取手柄在空间中的位置
- Pose:用于获取手柄的运动数据,包括位置和旋转信息
- Skeleton:提供用于呈现首部模型的谷歌数据,每个关节点的位置和旋转
了解按键的绑定
使用Action(动作)的优势:
- 更方便进行多硬件平台的适配,实现跨平台
- 针对单一平台,能减少需求变更时的代码修改工作
脚本获取动作输入
获取动作的两种方式:
1声明变量形式引用
例:public SteanVR_Action_Boolean Fire;
2直接获取
例:SteamVR_Actions.default_Fire
监听动作的三种方式:
- 可以同时注册监听:
SteamVR_Action.default_Fire.AddOnStateUpListener(FireActionStateUpHandler,SteamVR_Input_Sourse.Any);
- 也可以通过C#的事件监听:
SteamVR_Action.default_Fire.OnstateUp+=OnFireActionStateUp;
private void OnFireActionStateUp(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource) { throw new NotImplementedException(); }
private void FireActionStateUpHandler(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource) { throw new NotImplementedException(); }
若跳转场景后用不到功能,可对应有RemoveOnStateUpListener和-=
- 通过Update监听
如
if (SteamVR_Input.GetAction<SteamVR_Action_Boolean>("Fire").GetStateDown(SteamVR_Input_Sources.Any)) {
}
类似于
if (Input.GetKeyDown(KeyCode.Space))
{}
2 Interction System
Player,Hand的使用
注:
- player核心单例类
- Hand实现各种交互的基础
2019.4之后 unity提供 URP(通用渲染管线),HDRP(高清渲染管线)对手部控制器模型的渲染可能有问题
player:
- 命名空间:
using Valve.VR.InteractionSystem;
- 作为单例类的使用实例:
//获取手柄速度
Player.instance.rightHand.GetTrackedObjectVelocity();
Interctable组件
- 交互前提:有碰撞器
- 交互感知:Interctable
- 交互(抓取,抛掷):Throwable组件
与UI交互
控制器直接接触UI进行交互
- UI元素加碰撞器
- 添加UIElement组件
不直接接触进行交互(非Interctable组件)
凝视UI交互效果(头部射线)
- InputModule:SteamVR UI的事件系统,类比原EventSystem
示例:
public class HIdeGaze : MonoBehaviour
{
public Transform headForRaycast;//发射射线位置public Transform curssor;//光标
public Image progressImage;//光标图片
public Vector3 rayPositionoffset;//“眼睛”位置
public LayerMask layerMask;//参与交互的层级
private float rayLenth = 50f;
private GameObject currentInteractable;
private GameObject lastInteractable;
public float startTime;
public float activeTime=3;
public bool isEnable;
private void Awake()
{
if (curssor!=null)
{
//隐藏光标
curssor.gameObject.SetActive(false);
}//是否开启凝视
isEnable = true;
}// Update is called once per frame
void Update()
{
//必须要有发射射线的位置指定
if (headForRaycast==null)
{
return;
}if (progressImage!=null)
{
progressImage.fillAmount = 0;
}if (isEnable)
{
EyeRaycast();
}
}private void EyeRaycast()
{
Vector3 adjustedPosition = headForRaycast.position + (headForRaycast.right * rayPositionoffset.x) +
(headForRaycast.up * rayPositionoffset.y) + (headForRaycast.forward * rayPositionoffset.z);Ray ray = new Ray(adjustedPosition, headForRaycast.forward);
RaycastHit hit;
if (Physics.Raycast(ray,out hit,rayLenth,layerMask))
{
if (curssor!=null)
{
curssor.gameObject.SetActive(true);
curssor.position = hit.point;
curssor.rotation = headForRaycast.rotation;
}//获取游戏对象上的BUtton组件
Button aButton = hit.transform.GetComponent<Button>();
if (aButton==null)
{
//取消选择
DeactivateLastInteractable();//设置当前交互UI为空
currentInteractable = null;return;
}currentInteractable = aButton.gameObject;
//如果当前交互对象存在且当前交互对象不是上一次交互的对象,及找到新的对象
if (currentInteractable&¤tInteractable!=lastInteractable)
{
//通知BUtton目前有悬停事件
InputModule.instance.HoverBegin(currentInteractable);
}
else if (currentInteractable==lastInteractable)
{
//与上一次交互的对象是同一个,认为是停留,凝视时间积累
startTime += Time.deltaTime;if (progressImage!=null)
{
//填充效果
progressImage.fillAmount = (startTime / activeTime);
}if (startTime>activeTime)
{
//InputModule通知BUtton,此时为点击事件
InputModule.instance.Submit(currentInteractable);
//重置凝视时间
startTime = 0;
//取消选择UI
DeactivateLastInteractable();
//凝视交互不可用
isEnable = false;
//1秒后开启凝视交互
Invoke("ReEnable", 1f);}
}
//如果当前交互对象不是上一个交互对象,取消凝视交互
if (currentInteractable!=lastInteractable)
DeactivateLastInteractable();
//上一个交互对象设置为当前交互对象
lastInteractable = currentInteractable;
}
else//如果射线没有碰撞任何交互对象
{
DeactivateLastInteractable();
currentInteractable = null;
}
}private void DeactivateLastInteractable()
{
//凝视时间归零
startTime = 0;
//进度图像归零
if (progressImage!=null)
{
progressImage.fillAmount = 0;
}
//上一个交互对象为null,则返回
if (lastInteractable=null)
{
return;
}//INputModule通知上一个交互对象此时指针移出
InputModule.instance.HoverEnd(lastInteractable);
lastInteractable = null;//隐藏用于标识视线的光标
if (curssor!=null)
{
curssor.gameObject.SetActive(false);
}
}private void ReEnable() {
isEnable = true;
}
}
使用射线进行UI交互
- UI添加碰撞器
- 控制器添加SteamVr_laser Point组件
- 编写控制脚本,示例如下:
using UnityEngine;
using UnityEngine.EventSystems;
using Valve.VR.Extras;public class LaserInteraction : MonoBehaviour
{private SteamVR_LaserPointer _laser;
private GameObject uGUIElement;
public bool isEnabled = true;
private void Awake()
{
_laser = GetComponent<SteamVR_LaserPointer>();if (_laser!=null)
{
if (!isEnabled)
{
_laser.enabled = false;
return;
}
else
{
_laser.PointerIn += _laser_PointerIn;
_laser.PointerOut += _laser_PointerOut;
_laser.PointerClick += _laser_PointerClick;
}
}
}private void _laser_PointerClick(object sender, PointerEventArgs e)
{
IPointerClickHandler _pointerClickHandler = e.target.GetComponent<IPointerClickHandler>();
if (_pointerClickHandler != null)
{
_pointerClickHandler.OnPointerClick(new PointerEventData(EventSystem.current));
}
}private void _laser_PointerOut(object sender, PointerEventArgs e)
{
IPointerExitHandler _pointerExitHandler = e.target.GetComponent<IPointerExitHandler>();
if (_pointerExitHandler != null)
{
_pointerExitHandler.OnPointerExit(new PointerEventData(EventSystem.current));
}
}private void _laser_PointerIn(object sender, PointerEventArgs e)
{
IPointerEnterHandler _pointerEnterHandler = e.target.GetComponent<IPointerEnterHandler>();
if (_pointerEnterHandler != null)
{
_pointerEnterHandler.OnPointerEnter(new PointerEventData(EventSystem.current));
}
}
}
与3D物体交互
自定义物体的手部抓取姿态
- 3D物体身上添加组件,碰撞器,Interactable,Trowable,SteamVR_Skeleton_Poser,注:场景中需要有player
- 在SteamVR_Skeleton_Poser下调整手部关节,注意创建动作与保存动作
- 创建手部动作的另一部分,在SteamVR_Skeleton_Poser下的Blending Editor下进行两部分动作的融合,完成完整动作,要绑定手柄动作
- 编写脚本控制3D物体的动作符合手部动作,并将脚本挂在3D物体上
using UnityEngine;
using Valve.VR;
using Valve.VR.InteractionSystem;public class Gun : MonoBehaviour
{//板机游戏对象
public Transform TriggerGo;//扳机完全按下的角度
public Vector3 TriggerDownRotation;//扳机的初始角度
private Quaternion _triggerOriginRotation;private Interactable _interactable;
// Start is called before the first frame update
void Start()
{
_triggerOriginRotation = TriggerGo.localRotation;_interactable = GetComponent<Interactable>();
if (_interactable!=null)
{
_interactable.onAttachedToHand+= InteractableOnonAttachedToHand;
_interactable.onDetachedFromHand += InteractableOnonDetachedFromHand;
}
}private void InteractableOnonDetachedFromHand(Hand hand)
{
SteamVR_Actions.default_Squeeze.RemoveOnAxisListener(OnSqueezeAxis,hand.handType);
}
private void InteractableOnonAttachedToHand(Hand hand)
{
SteamVR_Actions.default_Squeeze.AddOnAxisListener(OnSqueezeAxis, hand.handType);
}
/// <summary>
///
/// </summary>
/// <param name="fromAction"></param>
/// <param name="fromSource"></param>
/// <param name="newAxis">手柄当前按下的角度</param>
/// <param name="newDelta">与上次相比的变化亮,正为按下,负为抬起</param>
private void OnSqueezeAxis(SteamVR_Action_Single fromAction, SteamVR_Input_Sources fromSource, float newAxis, float newDelta)
{
TriggerGo.localRotation = Quaternion.Lerp(_triggerOriginRotation, Quaternion.Euler(TriggerDownRotation), newAxis);
}
}
Item Package实现双手持握道具交互
- 创建空物体作为道具的位置,添加Item Package Swpaner组件
- 创建空物体作预制体,添加Item Package组件,指定双手单手,指定使用到的三个模型预制体
- 为模型预制体添加组件,Destroy on Detached from hand,itempackage Reference,
- 为道具预览位置添加碰撞器,实现交互
- 可选:上一部分知识,自定义物体的手部抓取姿态。
使用CircularDrive旋转3D物体
开关门示例:
- 把手部位添加碰撞体(在根物体添加)
- 添加CircularDrive组件,并进行设置
旋转阀门示例:
- 给阀门添加CircularDrive,linear Mapping,linear Displacement组件
- 在linear Displacement组件中在要移动的轴上输入移动距离
- 将Max Angle和out Angle设置为同一个值
使用LinearDrive平移3D物体
- 给抽屉添加碰撞体,LinearDrive组件
- 设置初始点和结束点