此教程相关的详细教案,文档,思维导图和工程文件会放入 Spatial XR 社区。这是一个高质量知识星球 XR 社区,博主目前在内担任 XR 开发的讲师。此外,该社区提供教程答疑、及时交流、进阶教程、外包、行业动态等服务。
社区链接:
Spatial XR 高级社区(知识星球)
Spatial XR 高级社区(爱发电)
往期回顾:
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
Unity VR 开发教程 OpenXR+XR Interaction Toolkit (六)手与物品交互(触摸、抓取)
VR 中的抓取功能除了近距离地与物体接触将其抓起(我把它称为 “直接抓取”)之外,还有一种通过射线抓取的方式。当射线射到物体上时,按下抓取键就能将其抓起。本篇教程,我将介绍如何用 XR Interaction Toolkit 实现射线抓取。
📕教程说明
使用的 Unity 版本: 2021.3.5
使用的 VR 头显: Oculus Quest 2
教程使用的 XR Interaction Toolkit 版本:2.3.2(此教程尽量考虑了向上兼容,如果有过期的地方,欢迎大家指出)
项目源码(持续更新):https://github.com/YY-nb/Unity_XRInteractionToolkit2.3.2_Demo
前期的配置:环境配置参考教程一,手部模型参考教程二。本篇教程的场景基于上一篇教程搭建的场景进行延伸。
最终实现的效果:
回顾上一篇直接抓取的教程,VR 中的交互一般需要两个对象:一个是可交互的对象(Interactable),一个是发起交互的对象(Interactor,一般是玩家自己)。直接抓取和射线抓取所抓取的对象并没有区别,因此 Interactable 需要的组件可以参考上一篇教程:Unity VR开发教程 OpenXR+XR Interaction Toolkit (六)手与物品交互(触摸、抓取)
而它们的区别正是 Interactor 的不同。直接抓取使用的是 XR Direct Interactor;射线抓取使用的是射线,如果你看过此系列教程的前几篇,应该对一个叫做 “XR Ray Interactor” 的组件有点印象,我们在传送和 UI 交互功能中有用到这个组件。它的功能是通过射线检测实现与物体的远距离交互,因此这个组件就是远距离抓取需要的 Interactor。
📕添加射线功能的相关组件
还是沿用上一篇教程中搭建好的场景。
我们分别在 XR Origin 下的 LeftHand Controller 和 RightHand Controller 物体创建两个子物体,可以鼠标右键点击 Ray Controller,选择 XR -> Ray Interactor(Action-based):
将这两个子物体的名字修改成 Distance Grab Interactor:
观察这两个子物体的 Inspector 面板,可以发现其中具备了 XR Interaction Toolkit 射线操作中需要用到的 XR Controller,XR Ray Interactor,Line Renderer, XR Interactor Line Visual 和 Sorting Group。
因为父物体已经有了配置完全一样的 XR Controller,所以我们可以把 DistanceGrab Interactor 上的 XR Controller 删掉,然后在对应方向设置 LeftHand 和 RightHand 的 Tag(这是上篇教程提到的,实现左右手切换抓取的一部分):
为了能有更好的效果,我们还可以设置 XR Ray Interactor 上的射线发射起始点 Ray Origin Transform 和 XR Interactor Line Visual 的颜色效果,这一部分可以参考 UI 交互的教程(Unity VR开发教程 OpenXR+XR Interaction Toolkit (五) UI),我这边就直接模仿 LeftHand UIController 和 RightHand UIController 的设置,让射线起始点位于食指处,射线无效是显示为透明(Invalid Color Gradient)。
在上一篇教程中,我还介绍了 XR Interaction Group 组件,位于 Left/RightHand Controller 游戏物体上,我们此时也把 DistanceGrabInteractor 物体拖到 Group 中,让 Group 保证其中一个 Interactor 被使用的时候,其他的 Interactor 暂时失效:
📕设置 Interaction Layer Mask
现在我们试着运行一下程序,当我们手部发出的射线射到可抓取的物体上时,按下手柄的抓取键就能将物体抓到手上。但是射线射到地面上时居然激活了传送。
这个问题曾经在 UI 交互的 Demo 中出现过,当时我们是修改 XR Ray Interactor 的 Raycast Mask 来过滤能被 UI 射线射到的目标。这种方式需要更改物体的 Physics Layer。
然而,我们还有一种解决方法,这里就要用到上一篇教程中提到的 Interaction Layer Mask,它依靠 Interactor 和 Interactable 中的 Interaction Layer 决定了 Interactor 能与哪些 Interactable 发生交互;而 Raycast Mask 是 XR Ray Interactor 特有的,它依靠物体的 Physics Layer(Inspector 面板最上方的那个 Layer)来决定射线能射到哪些物体的碰撞体上。
因为 XR Ray Interactor 的 Interaction Layer Mask 默认是 Everything,地面的 Interaction Layer Mask 是 Teleport(我们在之前的教程中修改过),Teleport 包含在 Everthing 中,所以射线抓取的射线能与传送地面交互。而可交互物体的 Interaction Layer Mask 默认是 Default,所以只要把 XR Ray Interactor 的 Interaction Layer Mask 改为 Default,就能解决这个问题。
当然,你也可以为直接抓取和射线抓取分别设立一个 Interaction Layer,来区分哪些物体能被直接抓取,哪些能被射线抓取,或者哪些既能被直接抓取,又能被射线抓取(Interaction Layer Mask 可以多选)
效果图:
📕防止射线抓取的射线与 UI 发生交互
这个时候,如果我们通过射线抓取的方式把物体抓到手上,再朝向场景中的 UI,会发现手部会发出射线射向 UI,并且按下手柄 Trigger 键还能与 UI 发生交互。
这时候手部的 UI 并不是 UI Ray Interactor 发出的射线,而是 DistanceGrab Interactor 发出的射线。
为了解决这个 BUG,我们找到 DistancegGrab Interactor 物体上的 XR Ray Interactor 组件,取消勾选 Raycast Mask 的 UI 选项,这样远距离抓取的射线就不会和 UI 发生响应了。
📕让 XR Direct Interactor 不对 XR Ray Interactor 产生干扰(新版 XRI 不会出现此问题)
注:下面这段内容是针对 XR Interaction Toolkit 2.1.1 的修改方法,此时还有一个 BUG,如下图所示,当我们用射线抓取了一个物体后,即使按住手柄 Grip 键不放,有时候也会不小心将另一个物体 “吸” 过来。但是如果你像我现在一样用的是 2.3.2 或者更高的版本,就不会出现这个问题,可以跳过这部分。
实际上,这是 XR Direct Interactor 对 XR Ray Interactor 产生了干扰。我这个场景中的物体既能被射线抓取,又能被直接抓取。当我用射线抓取将物体吸到手上时,XR Direct Interactor 会认为它抓到了物体,因为手柄的 Grip 键处于按下的状态,而且物体位于 XR Direct Interactor 的触发区域,所以触发了它的功能,而且此时这个被抓取的物体就不是由 XR Ray Interactor 管了,而是归 XR Direct Interactor 管。
与此同时,XR Ray Interactor 会继续发挥它的作用,通过射线检测判断是否与可交互的物体发生交互,所以它仍然会从起始点开始发射射线。我们在场景中看不到射线是因为 XR Interactor Line Visual 脚本只能渲染从起始点到第一个有效物体的碰撞体的那一部分射线,因为射线射到的第一个有效的物体是刚刚抓取的第一个物体,所以我们看不到渲染的射线,但是没被渲染的那部分射线仍然发挥着检测作用。所以当射线检测到另一个可交互的物体时,XR Ray Interactor 仍会发挥它的抓取作用。
解决思路也很简单,就是当 XR Direct Interactor 发挥作用时,将 XR Ray Interactor 隐藏掉。我们可以写一个脚本进行控制:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
public class GrabRayController : MonoBehaviour
{
public XRRayInteractor grabRayInteractor;
public XRDirectInteractor grabDirectInteractor;
void Update()
{
grabRayInteractor.enabled = grabDirectInteractor.interactablesSelected.Count == 0;
}
}
然后分别在 LeftHand Controller 和 Right Controller 物体挂载这个脚本,然后在 Inspector 面板中对变量进行赋值,那么这个 BUG 就被解决了。
📕使抓取的物体不会吸到手上 (Force Grab)
刚刚的 Demo 中,当射线射到可交互的物体后按下手柄的 Grip 键,物体会被立刻吸到手上。如果我想要抓取的时候让物体和手保持一定距离,营造一种远程操纵物体的感觉要怎么办呢?
我们找到 XR Ray Interactor 脚本,将 Force Grab 取消勾选。
如果想要让射线射到物体上的点作为实际的抓取点,而不是默认的抓取点,可以将 Dynamic Attach 勾选(上一篇直接抓取的教程中有介绍)
最终效果:
📕远距离抓取时通过摇杆改变抓取物体的位移和旋转角度(Rotate Anchor Action 和 Translate Anchor Action)
当你在测试射线抓取的场景时,也许会发现在使用取消 Force Grab 的射线抓取方式的前提下,如果按下手柄的 Grip 键抓取物体再推动摇杆,物体的位移和旋转角度为发生变化。
这是由 XR Controller(Action-based)和 XR Ray Interactor 组件的设置引起的。
首先看 XR Controller(Action-based)上的 Rotate Anchor Action 和 Translate Anchor Action:
它们引用了 Input System 中的 Action,我们找到默认的输入配置文件:
找到 Rotate Anchor 和 Translate Anchor:
可以看到这两个动作和摇杆在平面坐标轴上的位置有关,我们点击 Primary2DAxis,观察右侧的面板。
Rotate Anchor:
Translate Anchor:
关注其中的 Scale Vector 2,Rotate Anchor 的 X 是 1,说明在 X 轴上(左右)推动摇杆会触发这个动作;Translate Anchor 的 Y 是 1,说明在 Y轴上(前后)推动摇杆会触发这个动作。
然后我们再看 XR Ray Interactor 上的 Anchor Control:
正是因为这些设置,当我们远程抓取物体时,前后推动摇杆能控制物体与手的距离,左右推动摇杆能控制物体的旋转角度。
实际效果:
但是可以发现,触发 Rotate Anchor 或 Translate Anchor 的时候也会触发其他与摇杆控制有关的动作,比如传送,持续移动。因此我们希望在对远距离抓取物体进行操作时,不会触发该手柄其他和摇杆控制绑定的动作。
我们可以创建 Grab Ray Controller 脚本:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.XR.Interaction.Toolkit;
public class GrabRayController : MonoBehaviour
{
public XRRayInteractor grabRayInteractor;
public XRDirectInteractor grabDirectInteractor;
public InputActionReference rotateAnchorReference;
public InputActionReference translateAnchorReference;
public InputActionReference teleportActivateReference;
public InputActionReference moveReference;
public InputActionReference turnReference;
private void Start()
{
grabRayInteractor.selectEntered.AddListener(OnEnterGrab);
grabRayInteractor.selectExited.AddListener(OnExitGrab);
}
private void OnDestroy()
{
grabRayInteractor.selectEntered.RemoveListener(OnEnterGrab);
grabRayInteractor.selectExited.RemoveListener(OnExitGrab);
}
private void OnEnterGrab(SelectEnterEventArgs arg)
{
DisableAction(teleportActivateReference);
DisableAction(moveReference);
DisableAction(turnReference);
EnableAction(rotateAnchorReference);
EnableAction(translateAnchorReference);
}
private void OnExitGrab(SelectExitEventArgs arg)
{
EnableAction(teleportActivateReference);
EnableAction(moveReference);
EnableAction(turnReference);
DisableAction(rotateAnchorReference);
DisableAction(translateAnchorReference);
}
private void EnableAction(InputActionReference actionReference)
{
var action = GetInputAction(actionReference);
if (action != null && !action.enabled)
action.Enable();
}
private void DisableAction(InputActionReference actionReference)
{
var action = GetInputAction(actionReference);
if (action != null && action.enabled)
action.Disable();
}
private InputAction GetInputAction(InputActionReference actionReference)
{
return actionReference != null ? actionReference.action : null;
}
}
核心思想是在触发抓取(SelectEnter 事件触发)和取消抓取(SelectExit 事件)的时候去控制相关的 InputAction 是否能开启。
然后在 LeftHand Controller 和 RightHand Controller 物体上分别挂载这个脚本,并且手动赋值。
最终效果:
当然,如果你不想要远距离抓取时通过摇杆改变抓取物体的位移和旋转角度的功能,就直接将左右手 XR Controller 中的 Rotate Anchor Action 和 Translate Anchor Action 的 Use Reference 取消勾选,然后移除 Grab Ray Controller 脚本就行了。文章来源:https://www.toymoban.com/news/detail-536070.html
文章来源地址https://www.toymoban.com/news/detail-536070.html
到了这里,关于Unity VR 开发教程 OpenXR+XR Interaction Toolkit(七)射线抓取的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!