输入系统是 VR 开发中非常重要的一部分。我们通常需要获取 VR 手柄上某个按键的输入,然后将其作用到应用中,比如按下手柄的 Grip 键进行抓取,就需要在检测到“按下手柄 Grip 键”的输入操作时,执行抓取的行为。
SteamVR 插件是 Valve 提供给 Unity 开发者的用于开发 PCVR (头显与电脑串流的形式)的插件。随着越来越多 VR 设备的推出,开发者往往会面临一个难题,就是将自己开发的应用适配到不同的设备上,但是不同设备的手柄可能会有不同的按键,比如 Meta Quest 手柄上有摇杆,但是 Htc Vive 手柄上只有个圆形的触控板,作用和 Quest 手柄的摇杆是一样的。
Quest 手柄:
Htc Vive 手柄(下图的 2 是触摸板):
于是为了将项目适配到其他厂商的设备,开发者需要修改输入按键的代码以适配新的设备,比如开发者原先的项目是运行在 Htc Vive 上面的,其中有一个“通过触摸 Vive 手柄的触摸板,来控制人物移动”的功能。现在项目需要适配 Quest ,那么代码需要修改成“通过推动 Quest 手柄的摇杆,来控制人物移动”。其他的按键也是类似的道理,即使在游戏中实现的功能是一样的,但是因为设备的不同,开发者需要修改代码适配相应的按键。
因此,SteamVR 2.x 版本(2.0 以上的版本,笔者写这篇文章的时候已经更新到了 2.7.3 版本)推出了全新的输入系统 SteamVR Input。它是基于动作 Action 来开发,将输入设备和动作逻辑互相分离,通过配置映射来处理输入信息。也就是说,输入系统相当于程序之外的一个配置文件,我们可以在输入系统中将输入按键与动作进行绑定,这和 Unity 新输入系统 Input System 是类似的。使用这种方式的好处是:
用一张图来表示:
总结一下,开发人员不需要将输入视为某一特定设备的特定按键,而是在程序之外定义动作并与按键进行绑定,程序代码中关心的是“做出某个动作发生什么事情”,而不是“按下某个按键发生什么事情”。这样新的设备可以快速适配程序,无需更改代码,只需在输入系统配置文件中设置新设备的按键与动作的绑定关系。
目前这种基于动作而不是基于按键的输入系统会逐渐成为未来处理输入的主流,相比于直接在代码中监听输入设备,基于动作与输入相映射的方法可能会更复杂一点,因为我们除了要在代码中处理动作,还要额外创建一个配置文件将动作与输入操作进行绑定,但是它拥有移植方便、可拓展性高的优点,这适用于多设备、多平台的开发。
我使用的设备是 Meta Quest 2,使用 Meta Quest 2 开发 SteamVR 的前提是将 Quest 与电脑进行串流。如果你也是用 Quest 开发 SteamVR,首先电脑上要装一个 Oculus 电脑客户端(如下图所示),在电脑上打开它后,将头显连接电脑,然后在头显里点击 Oculus Link 进行串流,然后再连接 SteamVR。
使用的 Unity 版本: 2021.3.5
使用的操作系统:Windows 11
SteamVR 版本:2.7.3
我们可以在 Unity Asset Store 里搜索 SteamVR,将其添加进自己的资源。
然后在 Unity 中打开 Window/Package Manager:
在 My Asset 中找到 SteamVR Plugin,点击 Import 将其导入到项目中。
导入后可能会跳出下图的弹窗,点击 OK
如果出现了下图所示的弹窗,我们需要点击 Accept All,它会帮我们初始化一些配置,需要注意的是开发 SteamVR 的 Color Space 推荐使用的是 Linear(项目默认是 Gamma)
点击以后,建议重启一下项目,然后打开 Edit/Project Settings/XR Plugin Manager,确保勾选的是 OpenVR Loader,这样才能运行程序才会与 SteamVR 连接:
确认完毕后,可以在 Project 窗口中,路径 Assets/SteamVR/InteractionSystem/Samples下,打开场景文件Interactions_Example,这是 SteamVR 官方提供的一个交互场景,供开发者学习参考。
初次导入 SteamVR 插件并运行程序时,SteamVR 会检测项目是否存在动作以及动作与按键的绑定配置,如果没有,会打开一个弹窗询问是否打开SteamVR Input 窗口,我们选 Yes 就可以了。
在打开 SteamVR Input 窗口的过程中,SteamVR 插件会检测项目中是否存在 actions.json 文件,该文件存储了项目中动作(Action)与动作集(Action Sets)的信息,可以理解为输入系统的配置文件中存储了许多动作集,每一个动作集记录了一些动作与输入的映射关系。如果没有 actions.json 文件,插件会建议使用默认提供的示例文件,我们点击 Yes:
点击 Yes 按钮后,根据官方文档对这一操作的解释:
If you select your Window menu you’ll see a new item here called SteamVR Input. Click on that and you’ll likely get a dialog explaining that you’re missing an actions JSON and asking if you’d like to use the default. Select Yes and it’ll copy the default actions.json file, as well as the related bindings files for a few popular controllers into the root of your project directory. This is where SteamVR will read them from when you go into Play Mode and where it’ll copy them from when you make a build.
插件会将示例文件 actions.json 以及一些当前主流控制器的按键绑定配置文件拷贝到项目中的 Assets/StreamingAssets/SteamVR 目录下(如下图所示),未来在程序运行时,也将从此文件夹中读取用户关于动作的配置信息。
我使用的是 Quest 开发 SteamVR,这些按键绑定配置文件中有一个叫 Bindings_oculus_touch 的文件就是对应 Quest 的输入。
以上是点击 Yes 后插件会在背后做的事情,然后就会出现如下图所示的 SteamVR Input 窗口。之前有介绍过,action.json 文件存储了项目中动作与动作集的信息。这个时候,SteamVR 会读取 action.json 文件,在窗口顶部的 Action Sets 下列出记录的所有动作集(默认的有 defalut,platformer,buggy,mixedreality)。选择任一动作集,会在下方的 Actions 下列出这个动作集下的所有动作,我们可以在这个列表里添加或删除动作,In 的下方记录的是与输入有关的动作,Out 下方记录的是与输出有关的动作,比如 Haptic 动作与手柄的震动输出有关。选择任一动作,可以在窗口右侧的 Action Details 下看到这个动作配置的详细信息。
第一次打开这个窗口时,或者以后对这个窗口进行了修改,我们需要点击窗口下方的 Save and generate,它首先会把窗口中的动作配置信息保存在 action.json 文件中,然后会创建或更新一些动作类,之后可以在开发过程中通过代码对具体的动作进行引用,这些类的脚本位于 Assets/SteamVR_Input 目录下,如下图所示:
比如我点开一个 SteamVR_Actions 脚本,里面包含了刚刚在 SteamVR Input 窗口看到的一些动作变量(如下图所示),这些变量的数据类型(如下图中的 SteamVR_Action_Boolean,SteamVR_Action_Pose 等)是 SteamVR 为不同种类的动作设置的,我会在下一小节进行讲解。
以上便是对 SteamVR Input 窗口的简要介绍,至于具体如何使用这个输入系统窗口,稍后我也会进行讲解。
如果你不小心关闭了这个窗口,可以点击 Window/SteamVR Input 重新打开:
SteamVR 将动作的类型分为 6 个输入类型(Boolean,Single,Vector2,Vector3,Pose,Skeleton)和 1 个 输出类型(Vibration)。
官方文档:https://valvesoftware.github.io/steamvr_unity_plugin/articles/SteamVR-Input.html
Boolean 动作只返回 true 和 false 两种结果。检测是否按下手柄上的某个按键就能用 Boolean 类型表示,因为只有“按下按键”和“没按下按键”两种情况。比如我想在按下手柄 Grip 键的时候触发抓取,那么就是一个 Boolean 类型的动作检测为 true 时,触发抓取的逻辑。在 Unity 中对应类为 SteamVR_Action_Boolean。
Single 动作能够返回一个范围在 0-1 之间的数值。比如获取 Grip 键按下的程度,没按 Grip 键的时候 Single 的值为 0,随着逐渐按下 Grip 键,值会慢慢增大,按到底的时候值为 1。在 Unity 中对应类为 SteamVR_Action_Single。(注:Single 类型在 SteamVR Input 窗口中显示为 Vector1)
Vector2 动作能够返回一个二维向量,由 2 个值组成(x 和 y)。Vector2 类型经常用于表示手柄摇杆或触摸板的位置。因为摇杆或触摸板是在一个圆形范围内运动,我们可以将其想象为 x-y 坐标系下的一个圆心在原点,半径为 1 的圆,摇杆或触摸板运动后的位置就能用一个二维向量来表示。比如手柄摇杆向前方推到底,就会得到一个(0,1)的二维向量。如果需要推动摇杆或者触摸板来控制人物移动,就需要用到 Vector2 类型的动作。在 Unity 中对应类为 SteamVR_Action_Vector2。
Vector3 动作能够返回一个三维向量。在 Unity 中对应类为 SteamVR_Action_Vector3。
Pose 动作表示三维空间中的位置和旋转,一般用于跟踪 VR 手柄,比如虚拟的手部跟踪 VR 手柄的姿态,手柄的位置和旋转数据就会通过 Pose 动作传回程序,然后将数据赋予虚拟的手部,这样虚拟手部的位置和旋转就会和现实世界中的手柄相对应。在Unity中对应类为 SteamVR_Action_Pose。
Skeleton 动作能够获取用户在持握手柄时的手指关节数据,通过返回数据,结合手部渲染模型,能够更加真实的呈现手部在虚拟世界的姿态。这个动作一般是要结合手部模型,比如 Knuckles 指虎手柄拥有手指追踪的功能,可以估算用户手指的位置,然后将数据传递给程序,程序将其对应解析到手部模型的骨骼上,这样虚拟的手部骨骼姿态就能模拟现实中的手。除此之外,SteamVR 也有给像 Vive 或者 Quest 手柄提供手指状态估算的功能,比如判断手指是否放在触摸板或摇杆上,滑动触摸板或转动摇杆时会模拟手指关节的弯曲。在 Unity 中对应类为 SteamVR_Action_Skeleton。
Vibration 就是震动,与前面几种类型不同,它是一种输出类型,用于触发手柄上的震动反馈。
回顾刚刚介绍的 SteamVR Input 窗口:
我们选中一个动作后,在 Action Details 下方可以设置动作的名字、类型等属性。但是如果仅有这个窗口,我们还不知道这个动作与手柄的什么按键进行了绑定。因此,在 SteamVR Input 窗口创建了一个动作之后,或者想要修改原有动作的按键绑定,我们需要点击上图中 SteamVR Input 窗口中的 Open binging UI 按钮。点击后会出现如下界面(需要注意的是头显与 SteamVR 连接后才会打开如下界面):
因为我使用的是 Quest 2,所以显示的是 Oculus touch 控制器和与控制器匹配的绑定。点击编辑可以对动作和按键的绑定进行修改,会打开下图所示的界面,之后我们就是在这个界面里设置动作和按键的绑定关系:
比如现在我把鼠标光标移至 grabgrip 的板块上(如下图所示),它就会指向 Oculus Touch 的握持(Grip)键(Oculus Touch 是 Oculus Rift 设备的手柄,Quest 系列手柄的按键设置目前和 Rift 设备的手柄是一样的)
GrabGrip 是 SteamVR Input 窗口中定义的一个动作,此时它在 Binding UI 窗口中的名字是 grabgrip(没有区分大小写,和动作的名字是一样的),那么这个动作绑定的就是手柄的 Grip 键。然后我们可以点击下图中的像铅笔一样的符号:
可以看到“点击”的右侧对应的是 grabgrip,这个“点击”是什么意思呢?我们可以将鼠标光标移至“点击”文字处:
因为这个按键绑定属于扳机键的模块。所以它的意思就是按下 Grip 键时触发 grabgrip 这个动作。如果我们点击“更多选项”,界面会发生一些变化:
这时候“点击”变成了“单击”,界面也提供了更多的选项。严格来说,应该是按下一次 Grip 键触发 grabgrip 这个动作。除此之外,还有双击、长按、按压、触摸的选项,大家之后可以根据具体的开发需求进行选择。
另外,在 grabgrip 动作下方的一个板块是 squeeze 动作(如下图所示):
它们的区别是 grabgrip 动作绑定的东西是作为按键使用,触发条件是单击;squeeze 动作绑定的东西是作为扳机键使用,触发条件是扣动。作为按键使用和作为扳机键使用的区别是什么呢?我们可以点击“持握键”右侧的“+”号,然后会跳出如下图所示的界面,之后如果我们要为手柄按键添加动作的绑定也是这样操作。
也就是说,我们需要为手柄的某个按键选择一种操作的类型,也就是如何去使用这个按键。作为扳机键和作为按键是最常用的选项,我们可以点击右侧的问号,然后会显示使用说明。
作为扳机键:
作为按键:
这里作为扳机键的意思是像扳机键一样使用,因为扳机键有个按下的程度,所以和“作为按键使用”相比,它能够返回一个 0-1 之间的值。可以看到持握键作为扳机键使用时,扣动触发 squeeze 动作(如下图所示):
也就是扣动 Grip 键的时候,会返回一个 0-1 之间的值,没按 Grip 键的时候,返回 0,扣动 Grip 键直至按到底的时候,值会逐渐增大到 1。所以“作为扳机键使用”经常和 Single 类型的动作绑定。而作为按键使用则无法设置扣动的操作,它没法返回一个值,表示按键按下的程度,但是它在点击上提供了如双击之类的更多操作。
总结来说,我们可以在 SteamVR Input 窗口添加、删除、修改动作的属性,或者添加、删除动作集,这个界面的所有操作都是和动作有关,而 Binding UI 界面是用来为手柄按键绑定对应的动作,并且可以选择使用按键的方式,比如单击,长按,获取按键按下的程度等。
之前在介绍 SteramVR Input 窗口的时候,在面板的 Action Details 下有一个 Languages 和 Localized String 属性还没有介绍(如下图所示)。
Localized String 是本地化字符串的意思,Languages 是 Steam 页面的语言,它们和动作和按键绑定窗口 Bindigng UI 有一定的联系。此时我们点击 Open binding UI,打开配置界面:
当你在看其他 Unity SteamVR 开发教程的时候,可能会发现上图中用红框标出的动作名字变成了 SteamVR Input 窗口中 Localized String 的名字。但也许你的界面会和我一样,这些动作的名字是 SteamVR Input 窗口中 Action Details 下的 Name 的名字(不区分大小写)
这是因为只有 Steam 页面的语言和 SteamVR Input 窗口中的 Lanuages 设置一样时,Binding UI 中的动作名字才会和 Localized String 一样。我的 Steam 页面语言为简体中文,但是 SteamVR Input 窗口中的 Lanuages 只有一个 en_US(英语),所以 Binding UI 中的动作名字不是 Localized String,而默认是 SteamVR Input 窗口中 Action Details 下的 Name 的名字(不区分大小写)
现在,我将 Steam 页面的语言改成英语,需要在 Steam 中点击左上角的"Steam”,点击“界面”,然后修改 Steam 客户端语言:
改成英语后,重新打开 Binding UI,可以看到动作的名字变成了 Localized String 的名字:
但是如果我就想在 Steam 语言为简体中文的情况下开发,需要怎么做才能让 Binding UI 的动作名字与 Localized String 一样呢?
可以看到 SteamVR Input 窗口中的 Languages 是可以添加的,en_US 表示英语,那么我们只需要添加简体中文的语言代码。全世界的语言代码可以参考这个网址:http://www.lingoes.net/en/translator/langcode.htm(需要把“-”换成“_”),简体中文的语言代码为 zh_CN,因此我们可以在 SteamVR Input 窗口中的 Languages 下添加一个 zh_CN 语言代码,以 InteractUI 动作为例:
添加后记得点击 Save and generate,然后点击 Open binding UI:
可以看到 InteractUI 动作在 Binding UI 中的名字已经和语言代码是 zh_CN 下的 Localized String 的名字一样了。如果你打开 Binding UI 发现名字没有改变,可以尝试关闭 SteamVR Input 窗口,然后重新设置、保存,再打开 Binding UI。
Binding UI窗口默认是开启了镜像模式,这样只要配置了一边手柄,另一边手柄也会自动绑定。如果你想左右手柄绑定不同的动作,可以取消勾选镜像模式。
当我们配置好了动作,以及动作与按键的绑定关系后,我们需要在代码中引用动作。
首先我们简单地搭建下场景,在 Unity 中新建一个场景后,删除场景中的 Main Camera,添加一个平面,然后在项目的 Assets/SteamVR/Prefabs 文件夹中找到 [CameraRig] 预制体,将它拖入场景。这个预制体相当于 VR 中的玩家自己,它拥有头部摄像机,相当于虚拟世界中的眼睛,并且能够追踪手柄的姿态,运行程序后手部会渲染出当前使用的设备的手柄模型。
然后我们随便创建一个脚本添加到一个空物体上,待会儿我们就用这个脚本来演示:
要想引用 SteamVR 中设置的动作,先要在脚本中引用 Valve.VR 命名空间
using Valve.VR;
现在以“按下手柄 Grip 键”,也就是 Boolean 类型的动作为例,讲解如何用代码获取动作,以及判断动作是否触发。
之前介绍过,Boolean 动作在 Unity 中对应的类是 SteamVR_Action_Boolean,所以我们可以在脚本中声明一个 SteamVR_Action_Boolean 类型的公共变量:
public SteamVR_Action_Boolean booleanAction;
这样 Unity 编辑器中的 Inspector 面板就会显示这个变量:
我们可以在面板中选择一个动作赋予这个变量,根据 Binding UI 中的动作按键绑定关系,按下手柄 Grip 键对应的是 \actions\default\in\GrabGrip,意思是名为“defalut”的动作集下,输入类型(in)动作下的 GrabGrip 动作。现在,我们的脚本就已经连接了 SteamVR 的输入系统,之后我们就需要判断这个动作是否发生,以及编写动作发生后执行的事情。
我们可以这样编写脚本:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Valve.VR;
public class InputTest : MonoBehaviour
{
public SteamVR_Action_Boolean booleanAction;
void Start()
{
booleanAction.onStateDown += OnStateDown;
}
private void OnDestroy()
{
booleanAction.onStateDown -= OnStateDown;
}
private void OnStateDown(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
{
print($"{fromAction.activeDevice},{fromSource}");
}
void Update()
{
}
}
SteamVR_Action_Boolean 类中提供了一些事件,onStateDown 是在动作由 false 变为 true 的触发,在我们的场景下就是手柄 Grip 键由没按下变成按下的状态时触发。
其他事件的定义可以参考源码:
onChange //This event fires whenever a state changes from false to true or true to false
onUpdate //This event fires whenever the action is updated
onState //This event fires whenever the boolean action is true and gets updated
onStateDown //This event fires whenever the state of the boolean action has changed from false to true in the most recent update
onStateUp //This event fires whenever the state of the boolean action has changed from true to false in the most recent update
onActiveChange //Event fires when the active state (ActionSet active and binding active) changes
onActiveBindingChange //Event fires when the bound state of the binding changes
然后刚刚使用的 onStateDown 事件绑定的方法需要有 2 个参数,第一个是 SteamVR_Action_Boolean 类型,第二个是 SteamVR_Input_Sources 类型:
private void OnStateDown(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
{
print($"{fromAction.activeDevice},{fromSource}");
}
这个方法会在 GrabGrip 动作发生,也就是按下手柄 Grip 键时触发。如果我们运行程序,分别按下右手柄和左手柄的 Grip 键,就会在 Unity 控制台看到输出的文字:
fromAction.activeDevice 是个 SteamVR_Input_Sources 的枚举,它能够获取当前哪只手正在操作。fromSource 也是个 SteamVR_Input_Sources 的枚举,我们可以打开源码:
namespace Valve.VR
{
public enum SteamVR_Input_Sources
{
[Description("/unrestricted")] //todo: check to see if this gets exported: k_ulInvalidInputHandle
Any,
[Description("/user/hand/left")]
LeftHand,
[Description("/user/hand/right")]
RightHand,
[Description("/user/foot/left")]
LeftFoot,
[Description("/user/foot/right")]
RightFoot,
[Description("/user/shoulder/left")]
LeftShoulder,
[Description("/user/shoulder/right")]
RightShoulder,
[Description("/user/waist")]
Waist,
[Description("/user/chest")]
Chest,
[Description("/user/head")]
Head,
[Description("/user/gamepad")]
Gamepad,
[Description("/user/camera")]
Camera,
[Description("/user/keyboard")]
Keyboard,
[Description("/user/treadmill")]
Treadmill,
}
}
但是无论是按下左 Grip 键还是右 Grip 键,fromSource 返回的都是 Any。这是因为我们没有给定义的 SteamVR_Action_Boolean 类的变量指定输入源,如果我们这样操作:
booleanAction[SteamVR_Input_Sources.LeftHand].onStateDown += OnStateDown;
这时候 fromSource 就会返回 LeftHand。
如果你是选用为动作添加事件的方法,一定要记得在合适的地方移除事件,比如我就是在 OnDestroy 脚本销毁的时候移除事件:
private void OnDestroy()
{
booleanAction.onStateDown -= OnStateDown;
}
我们可以这样操作,在 Update 方法添加条件判断语句检测动作是否发生:
void Update()
{
if (booleanAction.stateDown)
{
print("手柄grip键按下");
}
}
SteamVR_Action_Boolean 类提供了几个公共的 bool 变量用于判断动作的发生:
/// [Shortcut to: SteamVR_Input_Sources.Any] True when the boolean action is true
public bool state { get { return sourceMap[SteamVR_Input_Sources.Any].state; } }
/// [Shortcut to: SteamVR_Input_Sources.Any] True when the boolean action is true and the last state was false
public bool stateDown { get { return sourceMap[SteamVR_Input_Sources.Any].stateDown; } }
/// [Shortcut to: SteamVR_Input_Sources.Any] True when the boolean action is false and the last state was true
public bool stateUp { get { return sourceMap[SteamVR_Input_Sources.Any].stateUp; } }
/// [Shortcut to: SteamVR_Input_Sources.Any] (previous update) True when the boolean action is true
public bool lastState { get { return sourceMap[SteamVR_Input_Sources.Any].lastState; } }
/// [Shortcut to: SteamVR_Input_Sources.Any] (previous update) True when the boolean action is true and the last state was false
public bool lastStateDown { get { return sourceMap[SteamVR_Input_Sources.Any].lastStateDown; } }
/// [Shortcut to: SteamVR_Input_Sources.Any] (previous update) True when the boolean action is false and the last state was true
public bool lastStateUp { get { return sourceMap[SteamVR_Input_Sources.Any].lastStateUp; } }
stateDown 变量就是在动作由 false 变为 true 时,返回 true。
这时候无论是左手柄还是右手柄都会触发,如果我们想要限定触发的手柄,可以这么做:
void Update()
{
if (booleanAction.GetStateDown(SteamVR_Input_Sources.LeftHand))
{
print("左手柄grip键按下");
}
}
或者
void Update()
{
if (booleanAction.stateDown)
{
if(booleanAction.activeDevice == SteamVR_Input_Sources.LeftHand)
{
print("左手柄grip键按下");
}
}
}
除了在 Inspector 面板中对动作进行赋值,我们也可以直接在代码中静态访问动作类:
SteamVR_Actions.default_GrabGrip.onStateDown += OnStateDown;
//或者
SteamVR_Actions._default.GrabGrip.onStateDown += OnStateDown;
这些动作类是之前在 SteamVR Input 窗口点击 Save and generate 后系统自动为我们创建的。SteamVR_Actions.default_GrabGrip 就是一个 SteamVR_Action_Boolean 类型。
同样我们可以通过在 Inspector 面板中赋值或者静态访问引用动作。我这里都选用 Inspector 面板中赋值的方式。
public SteamVR_Action_Single singleAction;
扣动 Grip 键能获取一个 0-1 之间的值。
singleAction.onAxis += OnSingleAction;
private void OnSingleAction(SteamVR_Action_Single fromAction, SteamVR_Input_Sources fromSource, float newAxis, float newDelta)
{
print($"newAxis:{newAxis},newDelta:{newDelta}");
}
newAxis 就是根据按下的程度返回的值,newDelta 是相较于上一个值的差值。因此我们可以通过 newAxis 获取 Single 动作的值。
除了 onAxis 事件,还有其他种类的事件,大家可以参考源码的解释:
/// [Shortcut to: SteamVR_Input_Sources.Any] This event fires whenever the axis changes by more than the specified changeTolerance
public event ChangeHandler onChange{ add { sourceMap[SteamVR_Input_Sources.Any].onChange += value; } remove { sourceMap[SteamVR_Input_Sources.Any].onChange -= value; } }
/// [Shortcut to: SteamVR_Input_Sources.Any] This event fires whenever the action is updated
public event UpdateHandler onUpdate{ add { sourceMap[SteamVR_Input_Sources.Any].onUpdate += value; } remove { sourceMap[SteamVR_Input_Sources.Any].onUpdate -= value; } }
/// [Shortcut to: SteamVR_Input_Sources.Any] This event will fire whenever the float value of the action is non-zero
public event AxisHandler onAxis{ add { sourceMap[SteamVR_Input_Sources.Any].onAxis += value; } remove { sourceMap[SteamVR_Input_Sources.Any].onAxis -= value; } }
/// [Shortcut to: SteamVR_Input_Sources.Any] This event fires when the active state (ActionSet active and binding active) changes
public event ActiveChangeHandler onActiveChange{ add { sourceMap[SteamVR_Input_Sources.Any].onActiveChange += value; } remove { sourceMap[SteamVR_Input_Sources.Any].onActiveChange -= value; } }
/// [Shortcut to: SteamVR_Input_Sources.Any] This event fires when the active state of the binding changes
public event ActiveChangeHandler onActiveBindingChange{ add { sourceMap[SteamVR_Input_Sources.Any].onActiveBindingChange += value; } remove { sourceMap[SteamVR_Input_Sources.Any].onActiveBindingChange -= value; } }
singleAction.axis
如果想要限定左右手柄,可以这么操作:
singleAction[SteamVR_Input_Sources.LeftHand].axis
singleAction[SteamVR_Input_Sources.RightHand].axis
singleAction.GetAxis(SteamVR_Input_Sources.LeftHand)
public SteamVR_Action_Vector2 vector2Action;
默认的 Vector2 类型有两个:
但是经测试发现,无法获取它们的值。因为这两个动作所属的动作集不是 default 默认动作集,所以一开始默认它们不是被激活的。至于如何激活其他动作集,我会在稍后进行讲解。
因此,我们可以自己在 defalut 动作集下创建一个 Vector2 类型的动作用于测试。首先打开 SteamVR Input 窗口,在 default 动作集下添加一个动作,把类型设为 Vector2:
然后点击 Save and generate,如果界面上出现了一个 compiling 代表成功。如果失败了大家可以重新打开 SteamVR Input 窗口,再试一次。
保存成功后点击 Open binding UI,进行按键绑定。
我们在 JoyStick 下(如果是 Htc Vive 应该是 Touchpad)添加一个绑定,作为摇杆使用,并且将“位置”设置为 joystick 动作。“位置”表示在触摸板上触摸的位置或者将摇杆推至的位置。因为此时处于镜像模式,所以我们新添加的动作就成功地和左右手柄的摇杆绑定好了。
然后在 Inspector 面板中对变量赋值:
现在就能够通过代码获取 Vector2 类型动作的值,获取方式和获取 Single 类型动作的值是一样的。
vector2Action.onAxis += OnVector2Action;
private void OnVector2Action(SteamVR_Action_Vector2 fromAction, SteamVR_Input_Sources fromSource, Vector2 axis, Vector2 delta)
{
print($"newAxis:{axis},newDelta:{delta}");
}
vector2Action.axis
vector2Action.GetAxis(SteamVR_Input_Sources.LeftHand)
public SteamVR_Action_Pose poseAction;
因为 Pose 动作对应的是手部的姿态,所以最常用的用法是获取手部的本地坐标和本地旋转角度
poseAction.localPosition
poseAction.localRotation
如果想要限定手柄,可以这么做:
poseAction[SteamVR_Input_Sources.LeftHand].localPosition
或者用方法获取:
poseAction.GetLocalPosition(SteamVR_Input_Sources.LeftHand);
SteamVR_Actions._default.Haptic.Execute(float secondsFromNow, float durationSeconds, float frequency, float amplitude, SteamVR_Input_Sources inputSource)
参数解释
secondsFromNow:从当前时间到执行震动动作之间需要多长的时间。也就是开始震动前需要多久的准备时间,也可以理解为震动的延迟时间。
durationSeconds:震动持续时间
frequency:震动马达多久反弹一次(范围是0-320hz)
amplitude:震动强度(范围0-1)
inputSource:输入源,一般指左右手柄
举个例子,我想在按下左手柄 Grip 键时震动左手柄,延续刚才的代码,可以这么做:
if (booleanAction.GetStateDown(SteamVR_Input_Sources.LeftHand))
{
print("左手柄grip键按下");
SteamVR_Actions._default.Haptic.Execute(0, 0.5f, 100, 0.5f, SteamVR_Input_Sources.LeftHand);
}
震动的参数可以自己调整。
点击 Window/SteamVR Input Live View 可以打开测试动作窗口:
运行程序后可以观察窗口变化:
SteamVR 为我们提供了几个动作相关脚本:
SteamVR_Behaviour_Boolean, SteamVR_Behaviour_Single, SteamVR_Behaviour_Vector2, SteamVR_Behaviour_Vector3, SteamVR_Behaviour_Pose, and SteamVR_Behaviour_Skeleton
我们可以把它们挂载到游戏物体上:
然后在面板上设置参数,原理和刚刚介绍的用代码获取动作是一样的,当然,我们也可以在自己写的脚本中去获取动作,判断动作是否发生。
使用脚本 SteamVR_ActivateActionSetOnLoad可以在场景中自动激活和停用指定的动作集。我们可以将它挂载到游戏物体上:
我们可以看看它的代码:
//======= Copyright (c) Valve Corporation, All rights reserved. ===============
using UnityEngine;
using System.Collections;
namespace Valve.VR
{
///
/// Automatically activates an action set on Start() and deactivates the set on OnDestroy(). Optionally deactivating all other sets as well.
///
public class SteamVR_ActivateActionSetOnLoad : MonoBehaviour
{
public SteamVR_ActionSet actionSet = SteamVR_Input.GetActionSet("default");
public SteamVR_Input_Sources forSources = SteamVR_Input_Sources.Any;
public bool disableAllOtherActionSets = false;
public bool activateOnStart = true;
public bool deactivateOnDestroy = true;
public int initialPriority = 0;
private void Start()
{
if (actionSet != null && activateOnStart)
{
//Debug.Log(string.Format("[SteamVR] Activating {0} action set.", actionSet.fullPath));
actionSet.Activate(forSources, initialPriority, disableAllOtherActionSets);
}
}
private void OnDestroy()
{
if (actionSet != null && deactivateOnDestroy)
{
//Debug.Log(string.Format("[SteamVR] Deactivating {0} action set.", actionSet.fullPath));
actionSet.Deactivate(forSources);
}
}
}
}
因此,之后我们也可以在自己的脚本中模仿这个代码激活或停用指定动作集。