一、回顾与引入
回顾上一篇直接抓取的教程,VR交互一般需要可交互的对象(Interactable)和发起交互的对象(Interactor)。直接抓取和射线抓取的可交互对象无区别,可参考上一篇教程设置组件。
两者区别在于发起交互的对象不同,直接抓取使用XR Direct Interactor,而射线抓取使用XR Ray Interactor,它可通过射线检测实现远距离交互。
二、添加射线功能的相关组件
-
在
XR Origin
下的LeftHand Controller
和RightHand Controller
物体创建子物体,选择XR -> Ray Interactor(Action-based
),并命名为Distance Grab Interactor
。 -
删除子物体上的
XR Controller
,设置LeftHand
和RightHand
的Tag
以实现左右手切换抓取。 -
设置
XR Ray Interactor
上的射线发射起始点Ray Origin Transform
和XR Interactor Line Visual
的颜色效果,可参考射线交互教程。 -
将
DistanceGrabInteractor
物体拖到Left/RightHand Controller
上的XR Interaction Group
中,确保一个Interactor被使用时其他Interactor暂时失效。
四、设置Interaction Layer Mask 层级,上一篇说过
-
运行程序时发现射线射到地面会激活传送,这是因为XR Ray Interactor的Interaction Layer Mask默认是Everything,而地面的Interaction Layer Mask是Teleport,可交互物体默认是Default。将XR Ray Interactor的Interaction Layer Mask改为Default可解决此问题。
-
也可分别为直接抓取和射线抓取设立不同的Interaction Layer,以区分可被不同方式抓取的物体。
五、防止射线抓取的射线与UI发生交互
- 当通过射线抓取物体后朝向UI,会发现手部发出射线并能与UI交互,这是DistanceGrab Interactor发出的射线。
- 找到DistancegGrab Interactor物体上的XR Ray Interactor组件,取消勾选Raycast Mask的UI选项,防止远距离抓取的射线与UI发生响应。
六、让XR Direct Interactor不对XR Ray Interactor产生干扰(新版XRI不会出现此问题)
-
注:此部分针对XR Interaction Toolkit 2.1.1版本,若使用2.3.2或更高版本可跳过。
-
当用射线抓取物体后,可能会出现即使按住手柄Grip键不放,也会不小心将另一个物体"吸"过来的问题,这是XR Direct Interactor对XR Ray Interactor产生干扰。
-
解决思路是写一个脚本控制,当XR Direct Interactor发挥作用时,将XR Ray Interactor隐藏掉。
-
创建脚本
GrabRayController
,在LeftHand Controller和Right Controller物体上挂载并赋值变量,可解决此问题。
csharp
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()
{
// 根据直接交互器选中的可交互对象数量来决定射线交互器是否启用。
// 如果直接交互器没有选中任何可交互对象(即数量为0),则启用射线交互器;否则禁用射线交互器。
grabRayInteractor.enabled = grabDirectInteractor.interactablesSelected.Count == 0;
}
}
七、使抓取的物体不会吸到手上(Force Grab)
-
若想在抓取时让物体和手保持一定距离,营造远程操纵物体的感觉,可取消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,可通过查看默认输入配置文件进行设置调整。
- XR Ray Interactor上的Anchor Control也与此功能相关。
- 当然,如果你不想要远距离抓取时通过摇杆改变抓取物体的位移和旋转角度的功能,就直接将左右手 XR Controller 中的 Rotate Anchor Action 和 Translate Anchor Action 的 Use Reference 取消勾选,然后移除下面新建的 Grab Ray Controller 脚本就行了。
但是可以发现,触发 Rotate Anchor 或 Translate Anchor 的时候也会触发其他与摇杆控制有关的动作,比如传送,持续移动。因此我们希望在对远距离抓取物体进行操作时,不会触发该手柄其他和摇杆控制绑定的动作。
csharp
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()
{
// 为射线交互器的 selectEntered 事件添加监听器,当抓取开始时调用 OnEnterGrab 方法
grabRayInteractor.selectEntered.AddListener(OnEnterGrab);
// 为射线交互器的 selectExited 事件添加监听器,当抓取结束时调用 OnExitGrab 方法
grabRayInteractor.selectExited.AddListener(OnExitGrab);
}
private void OnDestroy()
{
// 在对象销毁时,移除 selectEntered 事件的监听器
grabRayInteractor.selectEntered.RemoveListener(OnEnterGrab);
// 在对象销毁时,移除 selectExited 事件的监听器
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)
{
// 如果动作引用不为空,则返回对应的输入动作,否则返回 null
return actionReference!= null? actionReference.action : null;
}
}
然后在 LeftHand Controller 和 RightHand Controller 物体上分别挂载这个脚本,并且手动赋值。
通过以上步骤,我们实现了Unity VR中的射线抓取功能以及更多复杂的交互控制,为VR体验增添了丰富性和真实感。