[Unity学习教程] Unity VR 开发教程 OpenXR+XR Interaction Toolkit (四) 传送

[复制链接]
查看1324 | 回复0 | 2023-8-16 16:33:52 | 显示全部楼层 |阅读模式 来自 中国北京

往期回顾:
Unity VR 开发教程 OpenXR+XR Interaction Toolkit (一) 安装和配置
Unity VR 开发教程 OpenXR+XR Interaction Toolkit (二) 手部动画
Unity VR 开发教程 OpenXR+XR Interaction Toolkit (三) 转向和移动
上一篇教程中,我们学习了怎样用手柄来控制转向和连续移动。在 VR 应用中,除了连续移动尚有另一种比力常见的移动方式,那就是 “传送”,即让玩家直接到达想要传送的地点 。相较于连续移动,传送给人带来的晕动感不会那么剧烈。本篇教程,我们一起来学习怎样实现传送功能。

教程阐明

使用的 Unity 版本: 2021.3.5
使用的 VR 头显: Oculus Quest 2
教程使用的 XR Interaction Toolkit 版本:2.3.2
项目源码(连续更新):https://github.com/YY-nb/Unity_XRInteractionToolkit2.3.2_Demo
前期的配置:环境配置参考教程一,手部模型参考教程二
终极实现的效果:向前推动手柄的摇杆然后开释,实现人物的传送。


添加触发传送的脚本

传送其实也是交互的一种。而交互的过程一样寻常必要两个对象,一个是可交互的对象(Interactable),一个是发起交互的对象(Interactor,一样寻常是玩家本身)。传送的过程也是如此,必要可传送的地区和触发传送的人。现在,我们先来先容与触发传送相关的脚本。
Locomotion System:可以直接控制场景中的 XR Origin(在 VR 中就是我们本身的身段),从而实现 VR 中人物的运动。
Teleportation Provider:负责传送功能。
XR Ray Interactor:通过射线检测实现与物体的远间隔交互,在传送时可以共同可视化的射线使用。
Line Renderer:用于渲染传送时的射线
XR Interactor Line Visual:搭配 Line Renderer 和 XR Ray Interacter,使准备传送的射线可视化。
别的 XR Controller(Action-based) 这个脚本也是必备的,它用于跟踪手柄的姿态和处置处罚手柄输入和动作的绑定。而我们的传送功能也是通过手柄的按键触发的。(这个系列中的每一篇教程都会用到它,这篇教程再次强调它是由于待会儿要对这个脚本上的一些设置做些修改)
假如你跟着此系列教程的前三篇做了一遍,那么这些脚本其实就已经添加好了。我这里直接将上一篇教程中的大部门场景沿用下来,此时 Hierarchy 窗口如下图所示:

不外这里照旧再简要地提一下: Locomotion System 游戏物体可以直接在 Hierarchy 面板中的 XR 下创建。

添加了这个游戏物体就会自带 Teleportation Provider 和 Locomotion System 脚本。

而 XR Controller,XR Ray Interactor,Line Renderer 和 XR Interactor Line Visual 脚本在 XR Origin 下的 LeftHand Controller 和 RightHand Controller 上。


有些小同伴可能会对上图中 Sorting Group 的作用感到疑惑。这是创建 XR Origin 的时间在 Left/RightHand Controller 物体上主动添加的组件。通过控制 Sorting Group 上的参数,可以包管射线渲染在 UI 的前面。这部门会在我的 UI 交互教程中详细阐明,不外一样寻常的需求下,这个组件其实是无关紧要的。
固然这些脚本可以让 Unity 主动帮我们创建好,但是照旧希望大家能够记着必要用到哪些脚本,以及这些脚本大致的作用,以便日后能够根据我们的需求灵活使用。
末了,我们还必要对 XR Ray Interactor 脚本举行一些操纵:
在 Inspector 面板中将 Keep Selected Target 取消勾选。否则当你选中了一个传送地区后,你无法切换到另一个传送地区。

接下来,我们来先容一下传送必要的可交互对象。

添加传送地区脚本

⭐Teleportation Area 脚本

我们可以在地面(在本教程的场景中是 Plane)添加 Teleportation Area 脚本。
注:地面必须要有碰撞体,且碰撞体不能设为 Is Trigger,否则无法检测到传送地区

假如没有给此脚本的 Colliders 手动赋值,那么它会找到任意一个子物体(包括本身)的碰撞体,将该碰撞体用于检测是否选中了传送地区,假如触发传送的射线射到了碰撞体上,则视为选中了传送地区。
这时间我们可以运行一下程序。可以看到手部会发出一条直线。当直线射到地面上时,射线颜色会酿成白色,由于这时间我们选中了传送地区;当直线没有射到地面上,射线的颜色会酿成赤色。

按动手柄的 Grip 键,传送就会生效。至于为什么是按下 Grip 键才气传送,在稍后的 “向前推动手柄摇杆实现传送” 这一部门会举行详细地阐明。

⭐Teleportation Anchor 脚本

Teleportation Anchor 可以理解成传送的一个目的点,它能使玩祖传送到一个指定的位置和角度。
为了演示,我创建了一个新的 Plane,然后创建一个空物体叫做 “Teleportation Anchor",作为传送目的点,然后创建一个它的 Cylinder 子物体来表现可传送的地区,并且将 Cylinder 的碰撞体用于判定是否选中了传送地区。接着给 Teleportation Anchor 游戏物体添加 Teleportation Anchor 脚本。
为了演示能传送到特定角度的功能,我将传送目的点的旋转角度调了一下,让它 z 轴角度和相机 z 轴角度不雷同。


相较于 Teleportation Area,Teleportation Anchor 多了一个紧张的变量:Teleport Anchor Transform。这个变量就决定了传送的目的点是哪一个,默认是这个脚本所挂载的谁人游戏物体,当然你也可以自定义一个传送点。
这个时间我们试着运行程序,可以发现,当手部的射线射到我们设定的圆柱体上时,射线颜色才会酿成白色,阐明我们选中了传送地区。然后我们按动手柄的 Grip 键,我们就会被传送到圆柱体的谁人位置,准确来说就是我们提前设定的 Teleportation Anchor 的位置(可能会有一点点的偏差),实验多次传送到 Teleportation Anchor,每次传送的位置都黑白常相近的,至少都能传送到我们指定的地区。
不外我们可以发现,固然每次传送的 Position 是相近的,但是传送后人物面朝向的角度却照旧传送前面朝向的谁人角度。而我们的需求是传送后面朝向指定的角度,也就是传送后眼睛看向的方向和 Teleportation Anchor 的 z 轴所指向的方向一样,那这要怎么实现呢?


回到 Teleportation Anchor 脚本,找到 Match Orientation,改成 Target Up and Forward,意思是传送到目的点后的角度以目的点正上方为 y 轴,目的点正火线为 z 轴,这就与我们的需求匹配了。

现在我们再运行一下程序看看效果:

现在就成功传送到了特定的位置,特定的角度。

向前推动手柄摇杆实现传送

值得留意的是,现在是只有按动手柄的 Grip 键,传送才会生效。那么是在什么地方定义了 “按下 Grip 键开始传送” 呢?
我们来看 XR Controller 这个脚本(以左手为例),缘故原由就出在这个 Select Action 当中:

我们打开这个 Reference,可以看到 Select 这个动作绑定的是 “Grip 键按下” 这个操纵

但是,按照大多数 VR 游戏的习惯,传送一样寻常是向前推动手柄摇杆的时间被激活,然后开释摇杆举行触发,那么要怎么更改成这种方式来触发传送呢?
其实在 Input Action Asset 里,就有相关的动作。我们找到 XRI LeftHand Locomotion 的 Teleport Select 或者 Teleport Mode Activate,这两个动作都绑定了 Primary2DAxis,它表现的是摇杆的坐标位置(把摇杆的推动范围看作一个坐标系,不动摇杆的时间摇杆位于原点,推动摇杆后摇杆在 x 和 y 轴上的位置会发生偏移。

它们的区别可以看界面最右边的 Binding Properties
Teleport Select:

Teleport Mode Activate:

可以观察上面两幅图用红框标出的部门,最显著的区别是这个 Directions。
Teleport Select 是 Everything,意为摇杆推向任何方向都能激活传送。
Teleport Mode Activate 是 North,意为摇杆向北(向前)推才气激活传送。
由于我们想要通过向前推动摇杆来激活传送,以是我们将要选择的是 Teleport Mode Activate。然后,我们把 XR Controller 中的 Select Action 和 Select Action Value 都换成 Teleport Mode Activate

接下来可以运行程序,选中了传送地区后,向前推动摇杆,再松开摇杆,人物就能够传送了!
⭐为什么向前推动摇杆时角色不会立刻位移,而是比及松开摇杆才位移(Teleport Trigger)

这里可能有些小同伴会有些疑问,我们向前推动摇杆会触发 XR Controller 的 Select Action,也就是触发了传送,但是角色为什么不会立刻位移,而是要比及松开摇杆后才举行位移呢?
缘故原由出在 Teleportation Area/Teleportation Anchor 脚本上的 Teleport Trigger 上,我们看脚本的 Inspector 面板:

由于 Teleport Trigger 默认是 On Select Exited,以是会在退出 Select,也就是松开向前推的摇杆后才会激活传送的位移功能。

让传送的射线酿成曲线

在大多数游戏中,传送时的射线是曲线,或者是贝塞尔曲线。我们可以更改 XR Ray Interactor 脚本的配置来实现这个需求。

我们可以选择 Projectile Curve 或者 Bezier Curve,它们都能实现曲线的效果,只不外底层原剖析有些差别。我这边选择 Projectile Curve,然后将 Velocity 减小到了 8,Velocity 越大,曲线射到的间隔就越远。感爱好的小同伴也可以调试一下曲线的其他数值以及贝塞尔曲线的各个数值,调出得当本身的曲线。

然后运行程序,可以看到射线酿成了曲线:


在射线末了添加辅助瞄准地区

英文的说法是添加 Reticle,Reticle 有十字线、瞄准线的意思,大家可以遐想瞄准镜的样子。

在大部门 VR 游戏中,传送的射线末了一样寻常会有一个圆形的地区,也就是这个 Reticle,用于辅助瞄准,如许让传送看起来更直观。
那么我们就来简单地制作一个 Reticle。我创建一个 Cylinder 来表现 Reticle,然后更改缩放值,移除掉它的碰撞体。然后把这个物体做成 Prefab(预制体)。

⭐法一:在 XR Interactor Line Visual 脚本上添加 Reticle

把刚刚制作的 Reticle 预制体拖到 XR Interactor Line Visual 的 Reticle 当中:

由于大部门 VR 游戏是只用一边手柄来触发传送,以是我这里先规定左手负责传送,因此我把右手的传送功能关闭,只需把 XR Ray Interactor 脚本关闭就行了。那么我们来看一下实际的效果:


射线末了这个圆形的地区,就是我们本身添加的 Reticle。
⭐法二:在 Teleportation Area 或者 Teleportation Anchor 脚本上添加 Reticle

如图所示, 将我们准备好的 Reticle 赋到 Custom Reticle 处,终极出现的效果和法一是一样的:



实现向前推动摇杆才气表现传送射线

大部门 VR 游戏中,只有向前推动手柄的摇杆,才会表现传送的射线,然后开释手柄的摇杆,传送射线消散。
但是现在为止我们的 Demo 是在程序一运行的时间就会表现传送的射线。不外由于射线是由 XR Ray Interactor 控制的,以是改进的思绪其实也比力简单,就是我们可以自定义一个脚本,去控制 XR Ray Interacter 脚本的开启和关闭。由于射线的打开和关闭是及时检测的,以是我们应该每一帧去判定手柄是否触发了传送,以及一个传送的过程是否结束

2023.1.18更新:
上面的思绪在 2.1.1 版本的 XR Interaction Toolkit 可以见效,但是新版的 XR Interaction Toolkit 经实验,控制 XR Ray Interacter 脚本的开启和关闭的思绪已经失效,即使能控制射线的打开和关闭,也无法触发传送。因此,为了能向上兼容,我们换一种思绪:去控制 XR Ray Interacter 脚当地点游戏物体的表现和隐蔽

由于输入的动作是基于 Input System的,以是必要一些 Input System 方面的知识。
脚本代码如下:
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using UnityEngine.InputSystem;
  6. using UnityEngine.XR.Interaction.Toolkit;
  7. public class TeleportationController : MonoBehaviour
  8. {
  9.     public InputActionProperty m_teleportModeActivate;
  10.     public InputActionProperty m_teleportModeCancel;
  11.     private InputAction teleportModeActivate;
  12.     private InputAction teleportModeCancel;
  13.     public XRRayInteractor teleportInteractor;
  14.    
  15.     void Start()
  16.     {
  17.         teleportModeActivate = m_teleportModeActivate.action;
  18.         teleportModeCancel = m_teleportModeCancel.action;
  19.         EnableAction();
  20.     }
  21.   
  22.     private void OnDestroy()
  23.     {
  24.         DisableAction();
  25.     }
  26.     void Update()
  27.     {
  28.         if (CanEnterTeleport())
  29.         {
  30.             SetTeleportController(true);
  31.             return;
  32.         }
  33.         if (CanExitTeleport())
  34.         {
  35.             SetTeleportController(false);
  36.             return;
  37.         }
  38.     }
  39.     private void SetTeleportController(bool isEnable)
  40.     {
  41.         if (teleportInteractor != null)
  42.         {
  43.             teleportInteractor.gameObject.SetActive(isEnable);
  44.         }
  45.         
  46.     }
  47.     private bool CanEnterTeleport()
  48.     {
  49.         bool isTriggerTeleport = teleportModeActivate != null && teleportModeActivate.triggered;
  50.         bool isCancelTeleport = teleportModeCancel != null && teleportModeCancel.triggered;
  51.         return isTriggerTeleport && !isCancelTeleport; //判断是否触发传送且没有按下取消传送的键
  52.     }
  53.     private bool CanExitTeleport()
  54.     {
  55.         bool isCancelTeleport = teleportModeCancel != null && teleportModeCancel.triggered;
  56.         bool isReleaseTeleport = teleportModeActivate != null && teleportModeActivate.phase == InputActionPhase.Waiting;
  57.         return isCancelTeleport || isReleaseTeleport; //判断是否按下取消传送的键或者释放了之前推动的摇杆
  58.     }
  59.     private void EnableAction()
  60.     {
  61.         if (teleportModeActivate != null && teleportModeActivate.enabled)
  62.         {
  63.             teleportModeActivate.Enable();
  64.         }
  65.     }
  66.     private void DisableAction()
  67.     {
  68.         if (teleportModeActivate != null && teleportModeActivate.enabled)
  69.         {
  70.             teleportModeActivate.Disable();
  71.         }
  72.     }
  73. }
复制代码

脚本表明:


  • Input Action Asset 中有一个 Teleport Mode Cancel 的动作,绑定的是“按下 Grip 键”这个操纵,也就是按动手柄的 Grip 键可以取消传送,我们也将这个功能参加脚本。



  • 触发传送(表现传送射线)的条件:向前推动手柄摇杆,并且没有按下 Grip 键
  • 不表现传送射线的条件:按下 Grip 键或者手柄的摇杆处于原始的位置(向前推动摇杆并且开释终极也是回到了原始位置)
  • EnableAction 的作用:InputAction 类的变量必要调用它的 Enable 方法后才气被激活。
  1. teleportModeActivate.Enable();
复制代码
这是使用 InputSystem 的一种好习惯。不外实际上,这边不手动调用 Enable 方法也是可以的。假如我们的 InputAction 所属的配置文件被添加到了 Input Action Manager 脚本中(上面用到的 Input Action 来自于 XR Interaction Toolkit 提供的 XRI Default Input Actions 配置文件,如下图所示),就不必要手动调用 Enable 方法。


由于场景中的 Input Action Manager 脚本(这个脚本在第一篇环境配置教程中添加过)已经帮我们配置好了,如下图所示:


可以看到 Input Action Manager 脚本中的 EnableInput 方法已经为每一个配置的动作调用了 Enable 方法。由于担心大家之后用了其他 Input Action Asset 文件中的动作配置会忘记手动调用 Enable 方法导致无法吸收输入,或者忘了添加到 Input Action Manager 中,以是这边提一下这个细节。

由于是控制游戏物体的显隐,以是 Teleportation Controller 脚本和 XR Ray Interactor 脚本不能挂载到同一个物体上。否则 XR Ray Interactor 地点的游戏物体隐蔽时无法调用 Teleportation Controller 的功能。
因此,我们稍微改变一下 XR Origin 游戏物体的层级结构:

起首在 LeftHand Controller 或者 RightHand Controller 下创建 Teleport Interactor 物体(这取决于你设定哪只手负责传送,我这边为了日后拓展,干脆先在两边上都添加上这个物体,实际上我设定的是左手负责传送),先把 Teleport Interactor 物体隐蔽掉,它的表现与隐蔽会由我们刚写的 Teleport Controller 脚本举行控制。然后将之前教程中与传送有关的脚本挪到 Teleport Interactor 游戏物体上。

接下来我们要在 LeftHand Controller 上添加两个脚本(由于我这边设定左手负责传送),如下图所示:

表明一下,就是把刚刚写的 Teleportation Controller 脚本挂载到 LeftHand Controller 上,将 Input Action Asset 里的 Teleport Mode Activate 和 Teleport Mode Cancel 赋上。把手部的模型作为 LeftHand Controller 的子物体。必要留意的是,手部模型的父物体必须要有 XR Controller (Action-based) 脚本,且要开启 Enable Input Tracking 。由于必要用到装备输入的游戏物体必要 XR Controller 脚本的支持,并且打开 Tracking 才气精确追踪手柄控制器的姿态,从而让作为子物体的手部模型能跟动手柄运动,以是我们必要给 LeftHand Controller 物体额外添加一个 XR Controller(Action-based)脚本,此中的动作配置先用默认的就行,与之前负责传送的 XR Controller 差别。
这个时间,LeftHand Controller 和 Teleport Interactor 都有 XR Controller (Action-based) 脚本,假如我们运行程序,会发现传送射线的起始点位置不对。这是由于父子物体上的 XR Controller 都开启了 Tracking 导致的辩论。因此如下图所示,我们必要点击子物体 Teleport Interactor 上的 XR Controller (Action-based),把 Enable Input Tracking 取消勾选。

注:我们在 Teleportation Controller 脚本中赋值的两个 Input Action Reference 作用仅仅是为了表现和隐蔽射线,真正触发传送的照旧 Teleport Interactor 物体上 XR Controller (Action-based) 中的 Select Action(我们之前已经设置好了)

然后我们可以运行一下程序,这时间,只有向前推动摇杆才会表现传送的射线。
   注:假如你是 XR Interaction Toolkit 2.3 之前的版本,必要关注一下选用了哪种方式在射线末了添加 Reticle。假如选用的是在 XR Interactor Line Visual 脚本上添加 Reticle,那么你会发现这个 Reticle 在没有触发传送的时间会位于你的脚下(新版的 XR Interaction Toolkit 不会有这个题目),而我们希望的是和传送射线一样,只有向前推动摇杆的时间表现 Reticle。
此中一种方式是修改脚本,获取 Reticle 的引用,然后在 SetTeleportController 方法中去控制 Reticle 的表现和隐蔽。
不外,假如选用的是在 Teleportation Area 或者 Teleportation Anchor 脚本上添加 Reticle,无需任何修改。只要把 Reticle 做成预制体赋给 Custom Reticle,在射线选中可传送地区后,就会在可传送地区的碰撞体外貌天生 Reticle。
  但是此时尚有一个小题目,假如你照着这篇教程制作了 Teleportation Anchor,就会发现射到圆柱体外貌的时间也会表现 Reticle

这看起来有一些奇怪,由于传送的 Reticle 一样寻常是表现在传送地区的上外貌。不外,这个圆柱体只是我在本篇教程的前半段演示用的。在实际的开发中, Teleportation Anchor 一样寻常都是地面上的某一块地区,碰撞体也应该是高度非常小的一块,这个圆柱体也一样寻常是透明的,雷同于光柱的效果。因此在实际的项目中必要调试 Teleprtation Anchor 所检测的碰撞体的高度,让 Reticle 能够表现在目的地区的上外貌。
我这边将原来的圆柱体改成方体,由于方体的碰撞体更好调试一点。那么方体的上外貌面积就代表了用于演示 Teleportation Anchor 的可传送地区。
终极效果:

美化传送射线的方法

假如你想美化传送射线,可以更改 XR Interactor Line Visual 和 Line Renderer 脚本的配置。
先来看 XR Interactor Line Visual 脚本:

常用的有调解射线宽度(Line Width),选中传送地区时射线的颜色(Valid Color Gradient),未选中传送地区时射线的颜色(Invalid Color Gradient)
而 Line Renderer 是 Unity 中原有的组件,每个设置详细是什么意思可以参考官方文档。不外在美化传送射线上常常用的是更改射线的材质,假如你想让射线的材质效果看起来更悦目,可以自定义材质,然后更改下图中标出的这个部门:

那么本篇教程的内容就到此为止啦!大家快去制作属于本身的传送功能吧!

来源:https://blog.csdn.net/qq_46044366/article/details/127361736
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则