Unity VR开发教程 OpenXR+XR Interaction Toolkit (六)手与物品交互(触摸、抓取)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6


文章目录


往期回顾
Unity VR开发教程 OpenXR+XR Interaction Toolkit (一) 安装和配置
Unity VR开发教程 OpenXR+XR Interaction Toolkit (二) 手部动画
Unity VR开发教程 OpenXR+XR Interaction Toolkit (三) 转向和移动
Unity VR开发教程 OpenXR+XR Interaction Toolkit (四) 传送
Unity VR开发教程 OpenXR+XR Interaction Toolkit (五) UI


交互一般需要两个对象一个是可交互的对象Interactable一个是发起交互的对象Interactor一般是玩家自己。本系列教程中的传送功能也是交互的一种方式可传送的地面是可交互的对象手部发出的传送射线是发起交互的对象。而这篇教程将要介绍的是如何在 VR 世界中直接用双手与物品进行交互此时物品是可交互的对象手是发起交互的对象。


📕教程说明

使用的 Unity 版本 2020.3.36

使用的 VR 头显 Oculus Quest 2

教程使用的 XR Interaction Toolkit 版本2.1.1此教程尽量考虑了向上兼容如果有过期的地方欢迎大家指出

前期的配置环境配置参考教程一手部模型参考教程二。本篇教程的场景基于上一篇教程搭建的场景进行延伸。

项目源码持续更新https://github.com/YY-nb/Unity_XRInteractionToolkit_Tutorial2022/tree/master

最终实现的效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


📕VR 交互的类型

VR 中的交互一般分为三种类型

  • Hover 悬停 一般指的是发起交互的对象停留在可交互对象的交互区域。以手与物品交互为例假设物品表面为可交互区域当手触摸到物品悬停在物品的可交互区域则视为触发了 Hover。

  • Grab抓取这个概念好理解就是把物品抓起来。

  • Use使用“使用” 是基于 “抓取” 的。有时候我们可以继续使用正在抓取的物体触发它的一些特性。比如抓取一把枪视为 Grab然后按下枪的扳机发射子弹则视为 Use。

接下来我会详细讲解这三种类型的用法。那么首先我们需要拥有可交互对象和发起交互的对象让交互的条件成立然后再具体实现交互的类型。


📕发起交互的对象Interactor

⭐XR Direct Interactor 脚本

因为本教程场景中的 XR Origin 沿用了上一篇 UI 教程中的游戏物体所以我们先回顾一下 XR Origin 目前的结构

在这里插入图片描述

类似的我们很容易想到在 LeftHand Controller 和 RightHand Controller 下创建子物体然后添加 XR Controller (Action-based) 脚本关闭 Enable Input Tracking和专门负责抓取的脚本。

为了让双手成为发起交互的对象我们需要用到 XR Direct Interactor 脚本。

在这里插入图片描述

但是如果把子物体上 XR Controller 的 Tracking 关闭同个物体上的 XR Direct Interactor 将失去作用。这是因为抓取的时候也是需要判断手部的姿态然而关闭了追踪与其产生了矛盾。

因为作为父物体的 LeftHand/RightHand Controller 上的 XR Controller 开启了追踪所以我们只能将 XR Direct Interactor 挂载到父物体上。

在这里插入图片描述

XR Interaction Toolkit 2.3.0 及以上版本可以采用的做法
2.3.0 版本也可以用上面介绍的方法不过它新增了一个 XR Interaction Group 脚本https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.3/manual/xr-interaction-group.html可以使我们 XR Origin 的层级看起来更有条理。使用方法也可以参考开发包中的样例场景我这边再简单说明一下。

如果要使用 XR Interaction Group我们就可以在 LeftHand Controller 和 RightHand Controller 下分别创建 Direct Interactor 子物体将 XR Direct Interactor 和 Sphere Collider 移到这个子物体上由 Direct Interactor 物体单独处理手部的交互层级看起来更加清晰

在这里插入图片描述

在这里插入图片描述

我们在作为父物体的 LeftHand Controller 和 RightHand Controller 上分别添加 XR Interaction Group 脚本然后在 Starting Group Members 添加 Interactor

在这里插入图片描述

这就相当于把原来与发起交互有关的两个组件从父物体挪到了子物体上由父物体的 XR Interaction Group 统一控制子物体上的 Interactor。可能有的小伙伴会有疑问为什么这时子物体无需添加 XR Controller 呢

因为父物体的 XR Controller 绑定的 InputAction 和手部发起交互的 Input Action 是一样的。并且父物体的 XR Interaction Group 充当了 Interactor 的管理者让原本父物体上的 XR Controller 统一控制组内的 Interactor。有了 XR Interaction Group 脚本我们就可以用一个物体的 XR Controller 集中控制多个物体的 Interactor。

不过像传送这种 XR Controller 绑定的 Input Action 和父物体略微有些不同的情况就单独在子物体上添加 XR Controller(Action-based)并且关闭 Enable Input Tracking让它不受 XR Interaction Group 管理。

⭐添加可交互区域

XR Direct Interactor 需要一个 Trigger 类型的碰撞体作为可交互的区域并且这个碰撞体需要和 XR Direct Interactor 挂载到同一个游戏物体上。因此我这边直接在 LeftHand Controller 和 RightHand Controller 上分别添加一个 Sphere Collider调整 Radius并且把 Is Trigger 勾选。那么当可交互对象进入这个可交互区域后就可以进行交互。

在这里插入图片描述


📕可交互的对象Interactable

首先我们创建一个可交互的物品我这边用红色方块来表示。

在这里插入图片描述

在这里插入图片描述

⭐添加刚体

因为手与物品的交互基本上是基于物理效果的所以我们要为可交互的物体添加刚体。

⭐XR Simple Interactable 脚本

最简单的可交互脚本就是 XR Simple Interactable但是它没有自带抓取的功能。

我们在方块上添加 XR Simple Interactable 脚本

在这里插入图片描述

虽然 XR Simple Interactable 的官方文档没有说明需要添加刚体但是实测后发现物体有了刚体后XR Simple Interactable 脚本才会生效

另外要注意的是挂载可交互脚本的游戏物体还需要一个碰撞体。游戏运行后这个碰撞体会自动赋给 XR Simple Interactable 的 Colliders 数组。

在这里插入图片描述

⭐Interactable Events

为了让 XR Simple Interactable 的效果可视化我们可以添加几个功能
1当手触碰到方块时方块的颜色发生改变。而这个功能也是 VR 交互方式中很常见的 Hover即悬停在物品的可交互区域。
2触碰到方块后按下手柄 Grip 键方块的颜色变成蓝色。
3触碰到方块后按住手柄的 Grip 键再按下手柄的 Trigger 键方块的颜色变回红色。

以上的三个功能也分别模拟了 VR 中常见的三种交互方式悬停Hover、抓取Grab、使用Use。但是因为 XR Simple Interactable 脚本的局限性我们不能真正将物品抓起来而只能模拟 “抓取” 发生的事件。

这时候就要用到 XR Simple Interactable 脚本中的 Interactable Events里面包含了交互时会发生的一些事件可以看到里面有一个 Hover Entered也就是开始悬停在可交互区域触发的事件。然后我们可以手动设置更改方块的材质我这边想让方块被触碰后变成黄色。

在这里插入图片描述

然后我们要实现的第二个和第三个功能分别对应了 Select Entered 和 Activated 这两个事件。同样地我们在 Inspector 面板中手动绑定事件

在这里插入图片描述

看到 Select 和 Activate是不是觉得有些眼熟呢我们打开 XR Interaction Toolkit 中自带的输入配置文件 XRI Default Input Actions

在这里插入图片描述

可以看到 XRI LeftHand Interaction 或者 XRI RightHand Interaction 下就有 Select 和 Activate。Select 动作绑定的是 “按下 Grip 键” 这个操作Activate 动作绑定的是 “按下 Trigger 键” 这个操作。

在这里插入图片描述

而 Interaction Events 中的 Select 和 Activate 使用的就是 XRI Default Input Actions 配置文件中的这两个动作并且要注意的是Activate 动作必须要以 Select 动作的发生为前提Interactable Events 中的所有事件都是以“与可交互对象发生了交互”为前提。因此当我们的手触碰到方块后按下手柄的 Grip 键视为发生了 Select 动作触发了 “方块的颜色变成蓝色” 这个事件在手触碰到方块后按下手柄的 Grip 键的前提下继续按下 Trigger 键视为发生了 Activate 动作触发了 “方块的颜色变成红色” 这个事件。

在这里插入图片描述

这时候也许有人会有疑问在上一篇 UI Demo 中我们也有用到 Activate 这个动作作用是按下 Trigger 键与 UI 进行交互。虽然 XR Controller 中的 Select Action 绑定的是 Select 这个动作正常来说得先按下Grip 键才能触发 Select 动作。可是实际上当 UI 射线射到 UI 上时射线的颜色变成了白色说明此时已经进入了选中的状态。我们为什么不需要先按下 Grip 键呢

在这里插入图片描述

这是因为 Canvas 上的 Tracked Device Graphic Raycaster 脚本的特性。这个脚本能让 UI 被射线响应当射线射到 UI 上时自动进入选中的状态也就是触发了 Select 动作。然后在 Select 动作发生的前提下我们按下 Trigger 键就能与 UI 进行交互。因此 UI Demo 中的 Activate 也是以 Select 为前提。


⭐XR Grab Interactable 脚本

给可交互的物体添加上这个脚本就能实现真正的抓取。因为抓取对应的是 XRI Default Input Actions 配置文件中的 Select 动作而 Select 动作绑定的是 “按下手柄的 Grip 键” 这个操作所以当手部靠近可交互物体时按下手柄的 Grip 键就能抓取物体。

在这里插入图片描述

注要想使用 XR Grab Interactable 脚本必须给物体添加刚体组件。不过即使之前没有刚体添加 XR Grab Interactable 脚本也会自动给物体加上刚体。

⚡Movement TypeInstantaneous KinematicVelocity Tracking

在 XR Grab Interactable 脚本中比较重要的是三种 Movement TypeInstantaneous Kinematic 和 Velocity Tracking

在这里插入图片描述

为了以示区分我这边创建了三个挂载 XR Grab Interactable 脚本的方块红色方块对应 Instantaneous黑色方块对应 Kinematic绿色方块对应 Velocity Tracking

在这里插入图片描述
在这里插入图片描述

Instantaneous
物体的移动位置和姿态完全跟随了 Interactor手的移动。它是通过在每一帧更新 Position 和 Rotation 进行移动所以看上去物体跟随手部的移动是几乎没有延迟的。但是这种移动方式没有运用物理刚体的效果即使物体上有刚体。此时抓取的物体会穿过带有碰撞体的桌子并且和其他刚体方块的碰撞效果也不是基于物理的。

在这里插入图片描述

Kinematic
通过 Kinematic Rigidbody运动刚体进行移动跟随手部移动的过程中会有一些延迟。移动过程中物体不受力和碰撞的作用。所以此时物体触碰其他碰撞体不会受到反作用力比如物体还是能穿过带有碰撞体的桌子。但是可以对其他刚体Kinematic Rigidbody 除外施加物理效果比如此时移动的 Kinematic 方块能够推动其他放置在桌子上的方块。

在这里插入图片描述

Velocity Tracking
通过设置刚体的速度和角速度进行移动。跟随手部移动的过程中会有一些延迟。移动过程中带有刚体的物理效果比如会和带有碰撞体的桌子发生碰撞也可以对其他刚体产生力的效果。

在这里插入图片描述

⚡Attach Transform 抓取点

XR Grab Interactable 脚本中有一个 Attach Transform 变量可以赋值作为物体的抓取点。如果没有赋值将默认以物体的 position 作为抓取点。

在这里插入图片描述

这个变量有时候很有用。比如抓取一把枪那么物品的抓取点应该位于枪柄上。
现在我们造一把简易的枪来看看 Attach Transform 怎么使用当然大家也可以用自己的模型资源。

在这里插入图片描述

然后添加碰撞体刚体以及 XR Grab Interactable 脚本。这里我先把 Movement Type 设为 Instantaneous。此时如果运行程序会发现抓取枪的抓取点不是我们想要的样子。

在这里插入图片描述

这时候Attach Transform 就派上了用场。我们可以在枪的游戏物体下创建一个子物体叫做 Attach Point。

在这里插入图片描述
然后将 Attach Point 赋给 Attach Transform

在这里插入图片描述

接下来我们运行程序动态调整 Attach Point 的 Position 和 Rotation直到手能握住枪柄复制 Attach Point 的 Transform组件退出程序后将其粘贴至 Attach Point 原来的 Transform 组件。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

现在抓取点的效果看起来稍微好了一点大家也可以自行优化。但是这里还有一个问题刚刚的 Attach Point 对应的是右手的抓取点如果我用左手抓取枪会发现 Attach Point 的位置是不对的。具体的解决方法我会放在下一个部分的 “优化一左右手抓取” 进行详细说明。

值得注意的是仅设置 Attach Transform 只是粗略地抓取。因为它在物理效果上显得不是很真实可以看到物体和手之间还是有穿模的现象。如果要实现精细地抓取会稍微麻烦一点今后也会出相应的教程进行详细说明。

关于 XR Grab Interactable 脚本的其他变量设置大家可以参考官方文档
https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.1/manual/xr-grab-interactable.html

⚡代码实现 Use 功能制作简易手枪

接着上面枪的例子我们要怎么实现抓起枪后按下手柄 Trigger 键进行射击呢这个功能实际上就是 VR 交互中的 Use 功能。

联想 XR Simple Interactable 和 Interactable Events 的部分因为 XR Grab Interactable 脚本中也有一模一样的 Interactable Events所以我们可以在 Inspector 面板中绑定 Activate 触发时的事件。不过现在我想展示如何用代码来进行 Interactable Events 事件的绑定。

🔍核心脚本

我们创建一个脚本叫做 GunController把它挂载到枪的游戏物体上。

using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.XR.Interaction.Toolkit;public class GunController : MonoBehaviour{public GameObject bullet;public Transform spawnPoint;public float fireSpeed = 40;void Start(){XRGrabInteractable grabbable = GetComponent<XRGrabInteractable>();grabbable.activated.AddListener(FireBullet);}private void FireBullet(ActivateEventArgs arg){GameObject spawnBullet = Instantiate(bullet,spawnPoint.position,spawnPoint.rotation);spawnBullet.GetComponent<Rigidbody>().velocity = spawnPoint.forward * fireSpeed;Destroy(spawnBullet,5);}}

其中最重要的部分就是获取 XR Grab Interactable 中的 activated 事件然后通过 AddListener 绑定事件触发的函数

&#x1f50d;制作子弹碰撞检测方式设为 Continous Dynamic

然后我们可以创建一个子弹的 Prefab需要刚体和碰撞体

在这里插入图片描述
这里有个小坑需要注意一下就是我们最好要把子弹 Rigidbody 的 Collision Detection 设为 Continous Dynamic否则因为子弹是高速运动的有时候会检测不到和刚体的碰撞造成子弹直接从物体中间穿过去。

在这里插入图片描述

&#x1f50d;制作子弹发射位置

再创建一个枪的子物体叫做 Spawn Point作为子弹生成的位置。该物体的 z 轴箭头方向对应子弹发射的方向。

在这里插入图片描述

在这里插入图片描述
然后把子弹和 Spawn Point 赋给 Gun Controller

在这里插入图片描述

这时候枪的功能就制作好了。我们试着运行程序

在这里插入图片描述

大功告成&#x1f60a;

&#x1f50d;优化一左右手抓取判断哪只手与物体交互

到目前为止我们还有一个问题没有被解决就是枪的 Attach Transform 的为止只适用于右手的抓取。我们希望左右手的抓取点都是正确的但是枪的 XR Grab Interactable 脚本只能有一个 Attach Transform。那么我们其实可以动态地去切换左右手对应的 Attach Transform

首先我们要准备好左右手对应的 Attach Point作为切换用的 Attach Transform

在这里插入图片描述

然后我们需要修改之前写的 GunController 脚本 方法一

using System;using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.XR.Interaction.Toolkit;public class GunController : MonoBehaviour{public GameObject bullet;public Transform spawnPoint;public float fireSpeed = 40;private Transform leftHandAttachPoint;private Transform rightHandAttachPoint;private XRGrabInteractable grabbable;void Start(){leftHandAttachPoint = transform.Find("LeftHand Attach Point");rightHandAttachPoint = transform.Find("RightHand Attach Point");grabbable = GetComponent<XRGrabInteractable>();grabbable.selectEntered.AddListener(ChangeAttachTransform);grabbable.activated.AddListener(FireBullet);}private void ChangeAttachTransform(SelectEnterEventArgs arg){Transform interactor = arg.interactorObject.transform; if (interactor.name == "LeftHand Controller"){grabbable.attachTransform = leftHandAttachPoint;}else if (interactor.name == "RightHand Controller"){grabbable.attachTransform = rightHandAttachPoint;}}private void FireBullet(ActivateEventArgs arg){GameObject spawnBullet = Instantiate(bullet,spawnPoint.position,spawnPoint.rotation); 
        spawnBullet.GetComponent<Rigidbody>().velocity = spawnPoint.forward * fireSpeed;Destroy(spawnBullet,5);}}

核心思想就是给 XR Grab Interactable 的 Select Entered 事件绑定事件触发的函数通过判断是哪一个 Interactor 来决定切换成哪一个 Attach Transform。

刚刚我们将左右手切换抓取的功能写在了枪的控制器中但是这样代码的耦合性可能会比较高因为除了枪可能还会有其他的物体可以用左右手切换抓取。如果想要让左右手抓取的脚本更为通用我们可以新建一个脚本继承 XR Grab Interactable 方法二:

using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.XR.Interaction.Toolkit;public class XRGrabInteractableTwoAttach : XRGrabInteractable{public Transform leftAttachTransform;public Transform rightAttachTransform;protected override void OnSelectEntered(SelectEnterEventArgs args){if(args.interactorObject.transform.CompareTag("Left Hand")){attachTransform = leftAttachTransform;}else if(args.interactorObject.transform.CompareTag("Right Hand")){attachTransform = rightAttachTransform;}base.OnSelectEntered(args);}}

核心思想类似我们是重写了 XR Grab Interactable 当中的 OnSelectEntered 方法它会在 Select Entered 事件触发时被调用。

然后我们把 Gun 上的 XR Grab Interactable 脚本替换成 XRGrabInteractableTwoAttach而之前 GunController 当中和左右手抓取相关的代码就可以删除了。

另外我这边将左右手 Attach Transform 的赋值改为了拖动赋值并且用 Tag 来判断是哪一个 Interactor所以不要忘了在编辑器中进行赋值并且给 LeftHand Controller 和 RightHand Controller 加上 Tag 哦。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

最终效果

在这里插入图片描述

&#x1f50d;优化二防止传送射线误触发远距离抓取Interaction Layer Mask

如果大家的项目中沿用了传送的功能那么会发现一个 BUG当向前推动手柄摇杆发射传送射线的时候如果射线刚好射到可交互的物品上这个物品会 “附着”在射线上跟随射线移动效果就和抓取一样我们称这种现象为远距离抓取它是由射线来控制抓取这一部分我会在下一篇教程中进行详细说明。

在这里插入图片描述

因为 XR Grab Interactable 的抓取对应的是 Select 这个动作而激活传送射线也是对应 Select 所以此时我们是误打误撞触发了远距离抓取因此我们要想办法让传送射线不触发远距离抓取。实现方式也很简单我们需要对之前传送的相关组件进行一些设置。

首先我们找到负责发射传送射线的 XR Ray Interactor 脚本将关注点放到 Interaction Layer Mask 上。

在这里插入图片描述

在这里插入图片描述

Interaction Layer Mask 相当于交互的过滤器表示交互的层级。当发起交互的对象Interactor 和可交互的对象Interactable的 Interaction Layer Mask 至少有一个是相同的那么它们是可以被交互的。

XR Ray Interactor 作为 Interactor它的层级是 Everything。然后我们找到作为 Interactable 的 Teleport Area可以看到它的层级是 Default。

在这里插入图片描述
因为 Default 包含在 Everything 中所以满足 Interactor 和 Interactable 的 Interaction Layer Mask 至少有一个是相同的条件即二者可以交互。

我们再看手与物品交互过程中的 XR Direct Interactor 和 XR Grab Interactable它们的层级也分别是 Everything 和 Default。所以物品的 Default 也包含在传送射线的 Everything 中传送的射线是能够与物品进行交互的。

那么我们可以单独为传送设置一个层级。我们点击 XR Ray Interactor 的 Interaction Layer Mask再选择 Add Layer我们新建一个 Teleport 层级

在这里插入图片描述

然后将与传送有关的 XR Ray Interactor 和 Teleport Area 的 Interaction Layer Mask 改成 Teleport

在这里插入图片描述

在这里插入图片描述

然后把 XR Direct Interactor 的 Interaction Layer Mask 改为 Default这样用于抓取的 Interactor 和 Interactable 的层级是相同的不会对其他的交互方式产生干扰。当然我们也可以为抓取单独设立一个 Interaction Layer Mask。重要的是搞清楚 Interaction Layer Mask 的作用。

在这里插入图片描述

现在再次运行程序这个 BUG 就消失啦此时传送射线就只能和 Teleport Area 进行交互。&#x1f60a;

⚡其他功能一将与物体接触的地方作为抓取点Dynamic Attach

我们之前介绍的抓取功能通过设置 Attach Transform确定了物体被抓取后的位置与朝向。可以发现无论从物体上的哪个部位进行抓取被抓取后的姿态都是相同的。

有时候我们并不想要这种抓取方式而是希望手能直接抓在抓取的部位上。XR Grab Interactable 脚本也提供了这个功能。

现在我创建一个细长的 Cube作为该功能的测试物体。

在这里插入图片描述
添加碰撞体刚体。然后添加 XR Grab Interactable 脚本勾选其中的 Use Dynamic Attach

在这里插入图片描述

勾选后会自动跳出 Match Position 匹配抓取时的 PositionMatch Rotation匹配抓取时的 RotationSnap To Collider抓在物体的碰撞体上。这些选项可以根据需求进行更改。

效果

在这里插入图片描述


⭐XR Tint Interactable Visual 脚本

在这里插入图片描述

这个脚本可以挂载到可交互对象上当发起交互的对象Interactor悬停Hover或者选中Select 动作触发对应 Input System 中设置的 Select 映射关系一般和 “按下手柄 Grip 键” 进行绑定可交互的对象时能够暂时改变可交互对象的颜色。

调整 Tint Color 能够设置想要改变的颜色勾选 Tint On Hover 能够在 Hover 的时候改变颜色勾选 Tint On Selection 能够在 Select 的时候改变颜色。

在这里插入图片描述


阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6