Unity SteamVR 开发教程:SteamVR Input 输入系统(2.x 以上版本)
文章目录
- 📕前言
- 📕教程说明
- 📕导入 SteamVR 插件
- 📕SteamVR Input 窗口
- ⭐action.json 文件
- ⭐窗口面板
- ⭐SteamVR_Input 目录
- 📕SteamVR 动作的类型
- ⭐Boolean
- ⭐Single
- ⭐Vector2
- ⭐Vector3
- ⭐Pose
- ⭐Skeleton
- ⭐Vibration
- 📕动作和按键绑定窗口 Binding UI
- ⭐动作绑定案例讲解
- 🔍按键点击样例
- 🔍“作为按键使用”和“作为扳机键使用”的区别
- ⭐Localized String,Languages 与 Binding UI 的联系
- ⭐镜像模式
- 📕用代码获取动作
- ⭐获取 Boolean 类型的动作
- 🔍在 Inspector 面板中赋值
- 🔍判断动作发生方法一:添加事件
- 🔍判断动作发生方法二:条件语句
- 🔍静态访问
- ⭐获取 Single 类型动作的值
- 🔍通过事件获取
- 🔍通过变量获取
- 🔍通过方法获取
- ⭐获取 Vector2 类型动作的值
- 🔍自定义动作
- 🔍通过事件获取
- 🔍通过变量获取
- 🔍通过方法获取
- ⭐获取 Pose 类型动作的值
- ⭐手柄震动
- 📕测试动作窗口
- 📕SteamVR 内置的动作相关脚本
- 📕激活/停用动作集
📕前言
输入系统是 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 是类似的。使用这种方式的好处是:
- 我们在代码中关心的是定义的动作是否触发,以及触发后需要执行什么样的方法,而不用关心是否监听到设备的输入,因为设备的输入会由输入系统自动监听。比如我们在输入系统配置文件中定义一个 Grab 动作,将它与 “按下 Htc Vive 手柄的 Grip 键” 和 “按下 Quest 手柄的 Grip 键”这两个输入操作进行绑定。当我们检测玩家是否抓取某个物体的时候,不是在代码中检测 Htc Vive 手柄的 Grip 键或 Quest 手柄的 Grip 键是否被按下,而是检测 Grab 动作是否为 true,因为检测设备输入交给了 SteamVR 输入系统本身。如果检测为 true,则执行抓取相关的代码。
- 以后我如果想把需求从“按下手柄 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;
⭐获取 Boolean 类型的动作
现在以“按下手柄 Grip 键”,也就是 Boolean 类型的动作为例,讲解如何用代码获取动作,以及判断动作是否触发。
🔍在 Inspector 面板中赋值
之前介绍过,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_ulInvalidInputHandleAny,[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 变量用于判断动作的发生:
/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> True when the boolean action is true</summary>
public bool state { get { return sourceMap[SteamVR_Input_Sources.Any].state; } }/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> True when the boolean action is true and the last state was false</summary>
public bool stateDown { get { return sourceMap[SteamVR_Input_Sources.Any].stateDown; } }/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> True when the boolean action is false and the last state was true</summary>
public bool stateUp { get { return sourceMap[SteamVR_Input_Sources.Any].stateUp; } }/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> (previous update) True when the boolean action is true</summary>
public bool lastState { get { return sourceMap[SteamVR_Input_Sources.Any].lastState; } }/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> (previous update) True when the boolean action is true and the last state was false</summary>
public bool lastStateDown { get { return sourceMap[SteamVR_Input_Sources.Any].lastStateDown; } }/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> (previous update) True when the boolean action is false and the last state was true</summary>
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 类型。
⭐获取 Single 类型动作的值
同样我们可以通过在 Inspector 面板中赋值或者静态访问引用动作。我这里都选用 Inspector 面板中赋值的方式。
public SteamVR_Action_Single singleAction;
我们可以选择 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}");
}
输出结果:
newAxis 就是根据按下的程度返回的值,newDelta 是相较于上一个值的差值。因此我们可以通过 newAxis 获取 Single 动作的值。
除了 onAxis 事件,还有其他种类的事件,大家可以参考源码的解释:
/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> This event fires whenever the axis changes by more than the specified changeTolerance</summary>
public event ChangeHandler onChange{ add { sourceMap[SteamVR_Input_Sources.Any].onChange += value; } remove { sourceMap[SteamVR_Input_Sources.Any].onChange -= value; } }/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> This event fires whenever the action is updated</summary>
public event UpdateHandler onUpdate{ add { sourceMap[SteamVR_Input_Sources.Any].onUpdate += value; } remove { sourceMap[SteamVR_Input_Sources.Any].onUpdate -= value; } }/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> This event will fire whenever the float value of the action is non-zero</summary>
public event AxisHandler onAxis{ add { sourceMap[SteamVR_Input_Sources.Any].onAxis += value; } remove { sourceMap[SteamVR_Input_Sources.Any].onAxis -= value; } }/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> This event fires when the active state (ActionSet active and binding active) changes</summary>
public event ActiveChangeHandler onActiveChange{ add { sourceMap[SteamVR_Input_Sources.Any].onActiveChange += value; } remove { sourceMap[SteamVR_Input_Sources.Any].onActiveChange -= value; } }/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> This event fires when the active state of the binding changes</summary>
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)
⭐获取 Vector2 类型动作的值
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)
⭐获取 Pose 类型动作的值
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 为我们提供了几个动作相关脚本:
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
{/// <summary>/// Automatically activates an action set on Start() and deactivates the set on OnDestroy(). Optionally deactivating all other sets as well./// </summary>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);}}}
}
因此,之后我们也可以在自己的脚本中模仿这个代码激活或停用指定动作集。
相关文章:
Unity SteamVR 开发教程:SteamVR Input 输入系统(2.x 以上版本)
文章目录 📕前言📕教程说明📕导入 SteamVR 插件📕SteamVR Input 窗口⭐action.json 文件⭐窗口面板⭐SteamVR_Input 目录 📕SteamVR 动作的类型⭐Boolean⭐Single⭐Vector2⭐Vector3⭐Pose⭐Skeleton⭐Vibration &…...
PyTorch中,卷积层、池化层、转置卷积层输出特征图形状计算公式总结
在PyTorch中,卷积层(Convolutional Layer)、池化层(Pooling Layer,例如最大池化层)、以及转置卷积层(Transpose Convolutional Layer,也称为反卷积层或上采样层)的输出特…...
Git Cherry Pick命令
1. 简介 Git是一款分布式版本控制系统,它提供了许多强大的功能来管理代码的版本和变更。其中之一就是cherry-pick命令,它允许我们选择某个分支上的一个或多个提交,并将它们应用到当前分支上。这个功能非常有用,可以帮助我们在不合…...
算法:经典贪心算法--跳一跳[2]
1、题目: 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i j] 处: 返回到达 nums[n - 1] 的最小跳跃次数。生…...
Vue 和 React 前端框架的比较
一、什么是Vue? Vue[1] 是一个用于构建用户界面的渐进式、可逐步采用的 JavaScript 框架。它由 Evan You[2] 于 2014 年创建,并由一个活跃的开发者社区负责维护。 Vue 设计得非常轻量级、灵活和强大。它建立在一个基于组件的架构上,以组件为…...
【Java】什么是过滤器链(FilterChain )?哪些场景可以使用过滤器链?
文章目录 前言1、创建过滤器2、修改 web.xml3、运行项目并查看结果 前言 在一个 Web 应用程序中可以注册多个 Filter 程序,每个 Filter 程序都可以针对某一个 URL 进行拦截。如果多个 Filter 程序都对同一个 URL 进行拦截,那么这些 Filter 就会组成一个…...
Vue-video-player下载失败(npm i 报错)
Vue-video-player下载失败 最近在做项目时涉及到视频的播放组件,看了一下选择了Vue-video-player这个工具,实际在操作中是遇到许多问题的。 Q1:不支持谷歌 对于 “vue-video-player” 使用时出现 Adobe Flash 不再支持的提示,这是因为 Ado…...
数据在内存中的存储(1)
目录 1、整数在内存中的存储 原码、反码、补码: 2、大小端: 前提须知: 大小端存储方式: 字节的顺序: 概念: 判断机器是大端还是小端: 代码展示: 代码优化1.0: …...
LINUX常用命令练习
显示LINUX系统当前的日期和时间。 date以 yyyy/mm/dd的格式显示系统当前的日期 date %Y/%m/%d以 yyyy-mm-dd的格式显示系统当前的日期 date %Y-%m-%d查看在线用户信息 who显示当前月份的日历 cal显示2023年整年的日历 cal 2023显示2023年9月的日历 cal 9 2023查看LINUX系统的Sh…...
2022年全国研究生数学建模竞赛华为杯C题汽车制造涂装-总装缓存调序区调度优化问题求解全过程文档及程序
2022年全国研究生数学建模竞赛华为杯 C题 汽车制造涂装-总装缓存调序区调度优化问题 原题再现: 背景介绍 汽车制造厂主要由焊装车间、涂装车间、总装车间构成,每个车间有不同的生产偏好,如:焊装车间由于车身夹具的限制偏向最…...
文本直接生成3D游戏场景、功能,用ChatGPT方式开发游戏!
3D游戏开发平台Hiber3D通过谷歌的PaLM大语言模型,结合自身500多个模板库,以及数百万个成品3D场景进行微调,推出了一个全新游戏开发平台。 该平台在生成式AI加持下,用户可以像使用ChatGPT那样,通过文本问答方式快速创建…...
2023年会展行业研究报告
第一章 行业概况 1.1 定义 会展行业是一个多元化和复杂的领域,涵盖了许多不同的活动和功能。一般来说,会展业是指在一定的区域空间内,许多人聚集在一起形成的定期或者不定期,制度或者非制度,传递和交流信息的群众性的…...
【Redis】如何保证Redis缓存与数据库的一致性?
文章目录 1、四种同步策略2、更新缓存还是删除缓存2.1 更新缓存2.2 删除缓存 3、先操作数据库还是缓存3.1 先删除缓存再更新数据库3.2 先更新数据库再删除缓存 4、延时双删4.1 采用读写分离的架构怎么办? 5、利用消息队列进行删除的补偿 1、四种同步策略 想要保证缓…...
MATLAB中ischange函数用法
目录 语法 说明 示例 均值的变化 线性区的变化 矩阵数据 ischange函数的功能是查找数据中的突然变化。 语法 TF ischange(A) TF ischange(A,method) TF ischange(___,dim) TF ischange(___,Name,Value) [TF,S1] ischange(___) [TF,S1,S2] ischange(___) 说明 …...
【React + Ant Design】表单如何在前置项未填写时禁止后置项交互并提示
在 react antd 中,对表单做在前置项未填写时禁用后置项交互并提示的效果。 情景 最近有这么个需求,某个业务中,要填写一张表单,其中有这样两项:选择数据连接和选择数据表,数据表是数据连接下所拥有的表。…...
Linux学习之MySQL建表
MySQL查询1 MySQL查询2 表管理 #1. 建库#1)库名命名规则仅可以使用数字、字母、下划线、不能纯数字,区分字母大小写,具有唯一性,不可使用MySQL命令或特殊字符#创建数据表时可以查看一下默认的字符集,8.0后创建数据库…...
Redis哨兵集群的介绍及搭建
Redis 是一款开源的、内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。然而,作为一个单点服务,Redis 在面临硬件故障或者网络问题时可能会导致服务不可用。为了解决这个问题,Redis 提供了哨兵模式,一个…...
【zookeeper】zookeeper日常运维
本文将分享一些zookeeper在日常使用中一些维护经验。 zookeeper清理快照 脚本或者命令清理 zookeeper长时间运行,快照逐渐增多可能造成服务器磁盘被占满的情况,但我们不能贸然用rm命令删除快照文件,如果直接删完会导致丢失好多数据&#x…...
【工作记录】MQTT介绍、安装部署及springboot集成@20230912
背景 近期公司可能会有物联网设备相关项目内容,提前对用到的mqtt协议做预研和初步使用。 最初接触到mqtt协议应该是早些年的即时通讯吧,现在已经是物联网设备最热门的协议了。 作为记录,也希望能帮助到需要的朋友。 MQTT介绍 《MQTT 协议规…...
Flask 使用 JWT(一)
下面是一些 JWT 的使用场景: 1、 授权:这是 JWT 最常的使用场景。一旦用户登录,后续的每个请求都必须携带 JWT ,允许用户携带 Token 访问所有的路由、服务器和资源。单点登录时目前使用最广泛的一个场景,因为它开销小并且能够轻易的实现跨域访问。 2、信息交换:JWT Token…...
Oracle(1):Oracle简介
1 什么是 ORACLE ORACLE 数据库系统是美国 ORACLE 公司(甲骨文)提供的以分布式数据库为核心的一组软件产品,是目前最流行的客户/服务器(CLIENT/SERVER)或B/S 体系结构的数据库之一。 ORACLE 通常应用于大型系统的数据库产品。 ORACLE 数据…...
计算机网络篇之IP地址
计算机网络篇之IP地址 文章目录 计算机网络篇之IP地址概括IPv4地址IPv6地址分配总结 概括 IP地址是计算机网络中用于标识和定位设备的一组数字,IP地址分为IPv4和IPv6两种格式 IPv4地址 IPv4地址是32位的二进制数,通常表示为四个用点分隔的十进制数&am…...
webrtc-m79-测试peerconnectionserver的webclient-p2p-demo
1 背景 webrtc的代码中有peerconnectionclient和peerconnectionserver的例子,但是没有对应的web端的例子,这里简单的写了一个测试例子,具体如下: 2 具体操作 2.1 操作流程 2.2 测试效果 使用webclient与peerconnectionclient的…...
C#,《小白学程序》第十五课:随机数(Random)第二,统计学初步,数据统计的计算方法与代码
1 文本格式 /// <summary> /// 《小白学程序》第十五课:随机数(Random)第二,统计学初步,数据统计的计算方法与代码 /// 用随机数做简单的统计并用图形显示统计结果。 /// </summary> /// <param name&q…...
C# 子类如何访问子类的方法(同一父类)
在继承关系中,子类可以通过创建另一个子类的对象来访问其方法。下面是一个示例,展示了子类如何访问另一个子类的方法: public class Animal {public virtual void Speak(){Console.WriteLine("我是动物。");} }public class Cat :…...
《Docker 容器化的艺术:深入理解容器技术》
🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🐅🐾猫头虎建议程序员必备技术栈一览表📖: 🛠️ 全栈技术 Full Stack: 📚…...
gitlab配置hook,commit message的时候校验提交的信息
在 GitLab 中配置 Webhook 来调用 Java 接口以校验 commit 信息,是很多公司的一些要求,因为提交信息的规范化是必要的 不阻止commit的版本 在 GitLab 项目中进入设置页面。 在左侧导航栏中选择 “Webhooks”(Web钩子)。 在 We…...
ssh远程管理服务
ssh远程管理服务是什么 SSH是一个安全协议,在进行数据传输时,会对数据包进行加密处理,加密后在进行数据传输。确保了数据传输安全, 那SSH服务主要功能有哪些呢? 1.提供远程连接服务器的服务 1)linux远程连接协议&…...
C语言顺序表
文章目录 前言线性表顺序表静态顺序表动态顺序表 接口实现 前言 我们先补一下上篇博客落下的知识点: 首先说一下斐波那契的时间复杂度和空间复杂度: long long Fac(size_t N) {if(0 N)return 1;return Fac(N-1)*N; }还是说一下size_t代表的类型是unsi…...
滑动窗口详解
滑动窗口本质其实也是一种双指针算法,只是因为它维护的区间随着遍历的进行在不停变化,所以形象地称为“滑动窗口” 一、⻓度最⼩的⼦数组 题目要求找到满足条件的长度最小的子数组,我们先来想想暴力的做法,再来想想能不能优化&am…...
电商网站会员体制怎么做/郑州专业seo哪家好
1、纯CSS3实现人物摇头动画这次我们要来分享一款超级可爱的纯CSS3人物摇头动画,初始化的时候人物的各个部位是利用CSS3动画效果拼接而成,接下来就是人物听音乐的场景,一边听音乐一边摇着脑袋,十分陶醉的样子,周围还会出…...
科技因子网站建设方案/电商代运营公司排名
从字面上理解很简单resultType 就是返回的类型resultMap 返回的是一个结果集,这个结果集一般是用过resultMap节点来配置的,相应的type一般是一个Model。而resultType则就是类型,包括了,int,sring,以及类似model这样的Object类型。…...
自己电脑做的网站如何映射到公网/在线优化工具
在系统管理或者数据库管理中,经常要周期性的执行某一个命令或者SQL语句。对于linux系统熟悉的人都知道linux的cron计划任务,能很方便地实现定期运行指定命令的功能。Mysql在5.1以后推出了事件调度器(Event Scheduler),和linux的cron功能一样&…...
珠海市网站建设的公司/制作网站代码
距离2018年高考还有不到一个月的时间了,很多人在准备最后冲刺的同时,也在关心高考成绩。2018各地区高考成绩排名查询,高考各高中成绩喜报榜单尚未公布,下面是往年各地区高考成绩排名查询,高考各高中成绩喜报榜单,想要了解同学可以…...
网站备案贵州电话/网站制作郑州
为什么80%的码农都做不了架构师?>>> 切记 导入头文件哦 .h #import "sys/utsname.h"(NSString *)getCurrentDeviceModel;//设备号 获取 (void)getDevicesInfo;//app相关信息 .m (NSString *)getCurrentDeviceModel {struct utsname systemI…...
高端定制手机网站/关键路径
考真word2007手机版是提供在线备考服务的应用软件,收录了大量的考试真题可以练习,提醒丰富全面,难度不一,层层递进,还模拟考场,支持自动阅卷、评分,提供手把手教学、答案演示等功能,…...