社区链接:
SpatialXR社区:完整课程、项目下载、项目孵化宣发、答疑、投融资、专属圈子
往期回顾:
Unity VR 开发教程 OpenXR+XR Interaction Toolkit (一) 安装和配置
Unity VR 开发教程 OpenXR+XR Interaction Toolkit (二) 手部动画
Unity VR 开发教程 OpenXR+XR Interaction Toolkit (三) 转向和移动
Unity VR 开发教程 OpenXR+XR Interaction Toolkit (四) 传送
在 VR 的交互中,与 UI 进行交互是很常见的功能。本篇教程,我将介绍如何在 VR 世界中用射线进行 UI 的交互。
使用的 Unity 版本: 2021.3.5
使用的 VR 头显: Oculus Quest 2
教程使用的 XR Interaction Toolkit 版本:2.3.2(此教程尽量考虑了向上兼容,如果有过期的地方,欢迎大家指出)
项目源码(持续更新):https://github.com/YY-nb/Unity_XRInteractionToolkit2.3.2_Demo
前期的配置:环境配置参考教程一,手部模型参考教程二。本篇教程的场景基于上一篇教程搭建的场景进行延伸。
最终实现的效果:手部射线(一开始是看不见的)对准 UI 时,会显示一条指向 UI 的射线。按下手柄的 Trigger 键,能与可交互的 UI(如 Button,Toggle,Slider 等)进行互动。
首先在场景中创建一个 Canvas 游戏物体。基于 VR 应用的沉浸感,其中的 UI 应该是 3D 世界中的一部分,因此我们在制作 UI 的时候要把 Canvas 组件的 Render Mode 改为 World Space
然后调整一下 Canvas 的大小和位置,就可以在 Canvas 上添加 UI 了。这里我就加上一个 Button,一个 Toggle 和 一个 Slider:
把 Canvas 上原来的 Graphic Raycaster 组件删掉,添加 Tracked Device Graphic Raycaster 脚本。添加了这个脚本后,UI 就能被射线响应,比如被射线选中高亮。
在 EventSystem 游戏物体上添加 XR UI Input Module 脚本,并且把原先的 Standalone Input Module 脚本移除。XR UI Input Module 配合 Event System 组件,可以让 Input Action 中的动作配置作用于 VR 中的 UI,也可以用于 PC 端的键鼠调试,总的来说就是结合了 Input System,用于管理 XR 中 UI 的相关输入。
因为在我们的需求中,是用射线与 UI 进行交互,所以我们需要添加和射线相关的脚本。这时候我们可以联想一下我写的上一篇传送教程(Unity VR开发教程 OpenXR+XR Interaction Toolkit (四) 传送)。因为传送也有用到射线,所以我们完全可以用类似的思路实现 UI 射线,即分别在左右手的控制器上添加发送射线的相关脚本。
我们沿用上一篇传送教程中的 XR Origin (因此保留了传送功能),然后分别在 LeftHand Controller 和 RightHandController 下创建 UI Ray Interactor 游戏物体:
模仿传送功能,在 UI Ray Interactor 物体添加上 XR Ray Interactor(Line Type 为 Straight Line,因为我们希望 UI 的射线是一条直线),Line Renderer(可以复制 TeleportController 上的 Line Renderer 组件),XR Interactor Line Visual 脚本,Sorting Group 组件(不添加该组件也能实现功能,不过官方添加 XR Ray Interactor 的时候会附带一个 Sorting Group,下面也会介绍一下 Sorting Group 的作用)。
左右手都添加了这些组件后,就能够发射一条直的射线。我们可以运行一下程序,这时候会发现两只手都会射出一条直的射线,当射线射到 UI 上时,按下手柄的 Trigger 键能被可交互的 UI (Button,Toggle,Slider)响应,比如能用射线点击按钮,拖动滑动条。
这里有些小伙伴可能会有疑问,为什么 UI Ray Interactor 不用添加 XR Controller 脚本呢?而上一篇传送教程中控制传送射线的 Teleport Interactor 物体却添加了 XR Controller 脚本,还把其中的 Enable Input Tracking 给关闭了。
我们可以看 UI Ray Interactor 的父物体 LeftHand Controller 或 RightHand Controller,在父物体上已经有了 XR Controller:
因为父物体 XR Controller 面板上的这些 Action 同样适用于 UI 射线操作,所以我们只需要让父物体的 XR Controller 处理输入,作为子物体的 UI Ray Interactor 无需添加额外的 XR Controller。这时候子物体的 Input Action 就会沿用父物体 XR Controller 中设置的那些 Action。
而在传送功能中,作为子物体的 Teleport Interactor 需要添加 XR Controller,是因为传送的 Select Action 和父物体的 Select Action 不一样(见下图) ,所以我们需要在子物体上额外添加一个 XR Controller,并且将 Enable Input Tracking 关闭防止追踪的冲突。
作为父物体的 LeftHand Controller:
作为子物体的 Teleport Interactor:
下面这段是官方文档的版本变更日志中的介绍:
Changed the Ray Interactor GameObject created through the GameObject > XR create menu to have a Sorting Group to make it render in front of UI
在创建拥有 XR Ray Interactor 的物体时,会默认添加一个 Sorting Group 组件。这个组件是 Unity UGUI 中的一个组件,每个参数具体什么意思可以查看官方文档。通过控制该组件上的参数,能够控制 UI 射线渲染在 UI 之前。
我这里就演示其中一种用法,比如我可以将 Sorting Group 的 Order in Layer 设为 5:
然后找到场景中的 Canvas 物体 的 Canvas 组件:
只要 Canvas 组件中的 Order in Layer 小于或等于 Sorting Group 中的 Order in Layer, UI 射线就会始终渲染在 UI 的前面。大家可以自行设置两个 Order in Layer 的值感受一下,如果 Canvas 组件中的 Order in Layer 大于 Sorting Group 中的 Order in Layer,那么我们是看不到 UI 射线的。
不过,即使没有添加 Sorting Group 组件,UI 射线也是会渲染在 UI 之前,只不过这个组件给了我们自定义的功能。是否需要添加这个组件就看大家的需求了。
但是仍然存在一个问题,我们的 XR Interactor Line Visual 脚本规定了当 UI 射线被激活时,射线颜色为白色;未被激活时射线颜色为红色。见 XR Interactor Line Visual 的 Valid Color Gradient 和 Invalid Color Gradient:
但是当我们的 UI 射线射在地面上的时候仍然处于激活的状态,还会显示传送功能中射线末端的 Reticle,并且当我们按下手柄的 Grip 键,居然也会触发传送。(见下图,UI 射线射到地面上时颜色为白色,说明处于激活的状态)
这个问题其实和上一篇传送教程中出现的问题是一样的,此时我们拥有两种射线,一种是传送射线,被上一篇教程中我们自己写的 TeleportationController 脚本所控制;另一种是刚刚创建的 UI 射线,它使用的是默认的配置。
因为我们之前给地面添加了 Teleportation Area 脚本,默认情况下是当射线射到地面的碰撞体时,会视为选中了传送区域,然后因为负责 UI 射线的 XR Controller 中的 Select Action 默认绑定的是 XRI LeftHand/RightHand Interaction 下的 Select 动作,而 Select 动作又绑定了 “Grip 键按下” 这个操作,所以按下 Grip 键会响应到传送的功能。而且是本应该负责与 UI 交互的射线响应了传送的触发。
但我们希望的是 UI 射线只有射到 UI 上的时候才能被激活。所以我们要对能激活 UI 射线的目标做个过滤。解决办法也很简单,我们找到 LeftHand UIController 物体和 RightHand UIController 物体上挂载的 XR Ray Interactor 脚本,把 Raycast Mask 中的 Everything 改成 UI :
修改前:
修改后:
现在我们再运行一下程序,这时候 UI 射线只有射到 UI 上才会变成白色,而射到地面上显示的是红色,说明只有 UI 激活了 UI 射线。
但是这里还可以有个优化,在当前的程序中,我们会看到手部一直射出一条有颜色的射线,即使没有指向 UI,场景中依然存在一条红色的射线。而我们可以这样改进:让射线射到 UI 上时才能显示,而射到其他地方不显示射线。
改进方法很简单,找到 XR Interactor Line Visual 脚本,修改 Invalid Color Gradient,我们可以把它的透明度改为 0。这样,当射线射到不是 UI 的地方时,射线就是透明的,在我们的眼中就是不显示的。
点开 Invalid Color Gradient 后,找到左上角和右上角白色的角标,点击后找到 Alpha 值,将它改成 0 就能让射线在未激活状态下处于透明状态。
修改前:
修改后:
现在再次运行程序,这时候只有射线射到 UI 上的时候才会看到白色的射线。
现在我们的射线是从大拇指附近射出来的,但是如果我想改变射线发射的位置呢?
其实操作也很简单,我们在手部模型下创建一个子物体作为射线发射的起始点,我这里将起始点的位置移至食指的地方,相当于让射线从食指处发出,射线发射的方向与起始点本地坐标的 z 轴方向一致。
找到 XR Ray Interactor 脚本中的 Ray Origin Transform
然后将刚刚创建的射线起始点分别拖入对应的 XR Ray Interactor 脚本的 Ray Origin Transform
现在试着运行程序,射线就会从食指射出啦!😊
最近有好多小伙伴问我如何实现通过按下 VR 手柄的菜单键(一般位于左手柄上)来控制 UI 的显示和隐藏,那么我这里就介绍一种简单的做法。
首先我们要在 XRI Default Input Actions 中配置按下手柄菜单键的 Action:
然后在 XRI LeftHand 中点击下图所示的 “+” 号,会新增一个 Action,我们把它命名为 Menu
确保右侧面板的 Action Properties 中的 Action Type 为 Button,因为我们检测的是按键是否被按下。
接着点击 Menu下方的这个 Binding,点击右侧面板的 Path,选择 XR Controller:
选择 XR Controller (LeftHand):
选择 Optional Controls:
选择 menuButton:
最后别忘了点击面板上方的 Save Asset 进行保存:
接下来,我们需要去得到刚刚配置的 Action,并且判断 Action 是否被触发,我们可以写个脚本来控制:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class UIController : MonoBehaviour
{
public InputActionReference menu;
public Canvas canvas;
private void Start()
{
if(menu != null)
{
menu.action.Enable();
menu.action.performed += ToggleMenu;
}
}
private void ToggleMenu(InputAction.CallbackContext context)
{
canvas.enabled = !canvas.enabled;
}
}
然后我把这个脚本挂载到 LeftHand Controller 物体上,并且将刚刚添加的 Menu 和场景中的 Canvas 拖拽到 面板中:
现在运行游戏,通过按下左手柄的菜单键就能够控制 UI 的显示和隐藏了。