• Unity SteamVR 开发教程:SteamVR Input 输入系统(2.x 以上版本)


    📕前言

    输入系统是 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 是类似的。使用这种方式的好处是:

    1. 我们在代码中关心的是定义的动作是否触发,以及触发后需要执行什么样的方法,而不用关心是否监听到设备的输入,因为设备的输入会由输入系统自动监听。比如我们在输入系统配置文件中定义一个 Grab 动作,将它与 “按下 Htc Vive 手柄的 Grip 键” 和 “按下 Quest 手柄的 Grip 键”这两个输入操作进行绑定。当我们检测玩家是否抓取某个物体的时候,不是在代码中检测 Htc Vive 手柄的 Grip 键或 Quest 手柄的 Grip 键是否被按下,而是检测 Grab 动作是否为 true,因为检测设备输入交给了 SteamVR 输入系统本身。如果检测为 true,则执行抓取相关的代码。
    2. 以后我如果想把需求从“按下手柄 Grip 键进行抓取”改为“按下手柄 Trigger 键进行抓取”,那么我只需要在配置文件里将“按下手柄 Trigger 键”的输入操作与 Grab 动作绑定,形成映射关系,然后删除 Grab 动作与“按下手柄 Grip 键”的绑定,而代码不需要做任何改变。因为在代码中我们检测的是 Grab 动作是否触发,而不是检测什么具体的按键被按下。我们在代码中关心的是动作的触发,在程序之外的配置文件中关心的是动作与什么样的输入绑定,而监听输入完全交给了输入系统本身,不用我们自己编写监听输入相关的代码,这使得输入设备和代码逻辑互相分离,大大提高了可拓展性

    用一张图来表示:

    在这里插入图片描述

    总结一下,开发人员不需要将输入视为某一特定设备的特定按键,而是在程序之外定义动作并与按键进行绑定,程序代码中关心的是“做出某个动作发生什么事情”,而不是“按下某个按键发生什么事情”。这样新的设备可以快速适配程序,无需更改代码,只需在输入系统配置文件中设置新设备的按键与动作的绑定关系。

    目前这种基于动作而不是基于按键的输入系统会逐渐成为未来处理输入的主流,相比于直接在代码中监听输入设备,基于动作与输入相映射的方法可能会更复杂一点,因为我们除了要在代码中处理动作,还要额外创建一个配置文件将动作与输入操作进行绑定,但是它拥有移植方便、可拓展性高的优点,这适用于多设备、多平台的开发。


    📕教程说明

    我使用的设备是 Meta Quest 2,使用 Meta Quest 2 开发 SteamVR 的前提是将 Quest 与电脑进行串流。如果你也是用 Quest 开发 SteamVR,首先电脑上要装一个 Oculus 电脑客户端(如下图所示),在电脑上打开它后,将头显连接电脑,然后在头显里点击 Oculus Link 进行串流,然后再连接 SteamVR。

    在这里插入图片描述

    使用的 Unity 版本: 2021.3.5

    使用的操作系统:Windows 11

    SteamVR 版本:2.7.3


    📕导入 SteamVR 插件

    我们可以在 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 窗口

    ⭐action.json 文件

    在打开 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 下看到这个动作配置的详细信息。

    在这里插入图片描述

    ⭐SteamVR_Input 目录

    第一次打开这个窗口时,或者以后对这个窗口进行了修改,我们需要点击窗口下方的 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 动作的类型

    SteamVR 将动作的类型分为 6 个输入类型(Boolean,Single,Vector2,Vector3,Pose,Skeleton)和 1 个 输出类型(Vibration)。

    官方文档:https://valvesoftware.github.io/steamvr_unity_plugin/articles/SteamVR-Input.html

    ⭐Boolean

    Boolean 动作只返回 true 和 false 两种结果。检测是否按下手柄上的某个按键就能用 Boolean 类型表示,因为只有“按下按键”和“没按下按键”两种情况。比如我想在按下手柄 Grip 键的时候触发抓取,那么就是一个 Boolean 类型的动作检测为 true 时,触发抓取的逻辑。在 Unity 中对应类为 SteamVR_Action_Boolean。

    ⭐Single

    Single 动作能够返回一个范围在 0-1 之间的数值。比如获取 Grip 键按下的程度,没按 Grip 键的时候 Single 的值为 0,随着逐渐按下 Grip 键,值会慢慢增大,按到底的时候值为 1。在 Unity 中对应类为 SteamVR_Action_Single。(注:Single 类型在 SteamVR Input 窗口中显示为 Vector1)

    ⭐Vector2

    Vector2 动作能够返回一个二维向量,由 2 个值组成(x 和 y)。Vector2 类型经常用于表示手柄摇杆或触摸板的位置。因为摇杆或触摸板是在一个圆形范围内运动,我们可以将其想象为 x-y 坐标系下的一个圆心在原点,半径为 1 的圆,摇杆或触摸板运动后的位置就能用一个二维向量来表示。比如手柄摇杆向前方推到底,就会得到一个(0,1)的二维向量。如果需要推动摇杆或者触摸板来控制人物移动,就需要用到 Vector2 类型的动作。在 Unity 中对应类为 SteamVR_Action_Vector2。

    在这里插入图片描述

    ⭐Vector3

    Vector3 动作能够返回一个三维向量。在 Unity 中对应类为 SteamVR_Action_Vector3。

    ⭐Pose

    Pose 动作表示三维空间中的位置和旋转,一般用于跟踪 VR 手柄,比如虚拟的手部跟踪 VR 手柄的姿态,手柄的位置和旋转数据就会通过 Pose 动作传回程序,然后将数据赋予虚拟的手部,这样虚拟手部的位置和旋转就会和现实世界中的手柄相对应。在Unity中对应类为 SteamVR_Action_Pose。

    ⭐Skeleton

    Skeleton 动作能够获取用户在持握手柄时的手指关节数据,通过返回数据,结合手部渲染模型,能够更加真实的呈现手部在虚拟世界的姿态。这个动作一般是要结合手部模型,比如 Knuckles 指虎手柄拥有手指追踪的功能,可以估算用户手指的位置,然后将数据传递给程序,程序将其对应解析到手部模型的骨骼上,这样虚拟的手部骨骼姿态就能模拟现实中的手。除此之外,SteamVR 也有给像 Vive 或者 Quest 手柄提供手指状态估算的功能,比如判断手指是否放在触摸板或摇杆上,滑动触摸板或转动摇杆时会模拟手指关节的弯曲。在 Unity 中对应类为 SteamVR_Action_Skeleton。

    ⭐Vibration

    Vibration 就是震动,与前面几种类型不同,它是一种输出类型,用于触发手柄上的震动反馈。


    📕动作和按键绑定窗口 Binding UI

    回顾刚刚介绍的 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 界面是用来为手柄按键绑定对应的动作,并且可以选择使用按键的方式,比如单击,长按,获取按键按下的程度等。

    ⭐Localized String,Languages 与 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;
    
    • 1

    ⭐获取 Boolean 类型的动作

    现在以“按下手柄 Grip 键”,也就是 Boolean 类型的动作为例,讲解如何用代码获取动作,以及判断动作是否触发。

    🔍在 Inspector 面板中赋值

    之前介绍过,Boolean 动作在 Unity 中对应的类是 SteamVR_Action_Boolean,所以我们可以在脚本中声明一个 SteamVR_Action_Boolean 类型的公共变量:

    public SteamVR_Action_Boolean booleanAction;
    
    • 1

    这样 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()
        {
            
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    然后刚刚使用的 onStateDown 事件绑定的方法需要有 2 个参数,第一个是 SteamVR_Action_Boolean 类型,第二个是 SteamVR_Input_Sources 类型:

    private void OnStateDown(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
    {
         print($"{fromAction.activeDevice},{fromSource}");
    }
    
    • 1
    • 2
    • 3
    • 4

    这个方法会在 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,
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    但是无论是按下左 Grip 键还是右 Grip 键,fromSource 返回的都是 Any。这是因为我们没有给定义的 SteamVR_Action_Boolean 类的变量指定输入源,如果我们这样操作:

    booleanAction[SteamVR_Input_Sources.LeftHand].onStateDown += OnStateDown;
    
    • 1

    这时候 fromSource 就会返回 LeftHand。

    如果你是选用为动作添加事件的方法,一定要记得在合适的地方移除事件,比如我就是在 OnDestroy 脚本销毁的时候移除事件:

        private void OnDestroy()
        {
            booleanAction.onStateDown -= OnStateDown;
        }
    
    • 1
    • 2
    • 3
    • 4

    🔍判断动作发生方法二:条件语句

    我们可以这样操作,在 Update 方法添加条件判断语句检测动作是否发生:

    	void Update()
        {
            if (booleanAction.stateDown)
            {
                print("手柄grip键按下");
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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; } }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    stateDown 变量就是在动作由 false 变为 true 时,返回 true。
    这时候无论是左手柄还是右手柄都会触发,如果我们想要限定触发的手柄,可以这么做:

    	void Update()
        {
            if (booleanAction.GetStateDown(SteamVR_Input_Sources.LeftHand))
            {
                print("左手柄grip键按下");
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    或者

    	void Update()
        {
            if (booleanAction.stateDown)
            {
            	if(booleanAction.activeDevice == SteamVR_Input_Sources.LeftHand)
            	{
            		print("左手柄grip键按下");
            	}
                
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    🔍静态访问

    除了在 Inspector 面板中对动作进行赋值,我们也可以直接在代码中静态访问动作类:

    SteamVR_Actions.default_GrabGrip.onStateDown += OnStateDown;
    //或者
    SteamVR_Actions._default.GrabGrip.onStateDown += OnStateDown;
    
    • 1
    • 2
    • 3

    这些动作类是之前在 SteamVR Input 窗口点击 Save and generate 后系统自动为我们创建的。SteamVR_Actions.default_GrabGrip 就是一个 SteamVR_Action_Boolean 类型。

    ⭐获取 Single 类型动作的值

    同样我们可以通过在 Inspector 面板中赋值或者静态访问引用动作。我这里都选用 Inspector 面板中赋值的方式。

    public SteamVR_Action_Single singleAction;
    
    • 1

    在这里插入图片描述
    我们可以选择 Squeeze 动作:

    在这里插入图片描述

    扣动 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}");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    输出结果:
    在这里插入图片描述

    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; } }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    🔍通过变量获取

    singleAction.axis
    
    • 1

    如果想要限定左右手柄,可以这么操作:

    singleAction[SteamVR_Input_Sources.LeftHand].axis
    singleAction[SteamVR_Input_Sources.RightHand].axis
    
    • 1
    • 2

    🔍通过方法获取

    singleAction.GetAxis(SteamVR_Input_Sources.LeftHand)
    
    • 1

    ⭐获取 Vector2 类型动作的值

    public SteamVR_Action_Vector2 vector2Action;
    
    • 1

    默认的 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}");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    🔍通过变量获取

    vector2Action.axis
    
    • 1

    🔍通过方法获取

    vector2Action.GetAxis(SteamVR_Input_Sources.LeftHand)
    
    • 1

    ⭐获取 Pose 类型动作的值

    public SteamVR_Action_Pose poseAction;
    
    • 1

    在这里插入图片描述

    因为 Pose 动作对应的是手部的姿态,所以最常用的用法是获取手部的本地坐标和本地旋转角度

    poseAction.localPosition
    poseAction.localRotation
    
    • 1
    • 2

    如果想要限定手柄,可以这么做:

    poseAction[SteamVR_Input_Sources.LeftHand].localPosition
    
    • 1

    或者用方法获取:

    poseAction.GetLocalPosition(SteamVR_Input_Sources.LeftHand);
    
    • 1

    ⭐手柄震动

    SteamVR_Actions._default.Haptic.Execute(float secondsFromNow, float durationSeconds, float frequency, float amplitude, SteamVR_Input_Sources inputSource)
    
    • 1

    参数解释

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    震动的参数可以自己调整。


    📕测试动作窗口

    点击 Window/SteamVR Input Live View 可以打开测试动作窗口:
    在这里插入图片描述

    运行程序后可以观察窗口变化:

    在这里插入图片描述

    📕SteamVR 内置的动作相关脚本

    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);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    因此,之后我们也可以在自己的脚本中模仿这个代码激活或停用指定动作集。

  • 相关阅读:
    HIVE实战处理(二十二)股票连续上涨最长的天数
    SpringBoot学习笔记(三)自动装配
    java学习day53-54(SSM)SSM权限操作
    jxTMS设计思想之流程开发(一)
    如何利用curl仿造websocket请求?
    电路方案分析(十三)采用 CAN 的汽车分立式 SBC 预升压、后降压参考设计方案
    《数据库系统概论》-02 中级SQL 约束、授权、索引
    Superset (三) --------- Superset 使用
    docker安装efk
    玩转C语言:深入理解输入输出函数的奥秘
  • 原文地址:https://blog.csdn.net/qq_46044366/article/details/132718085