一、VR交互的类型
- Hover(悬停)
- 定义:发起交互的对象停留在可交互对象的交互区域。例如,当手触摸到物品表面(可交互区域)时,视为触发了Hover。
- Grab(抓取)
- 概念:把物品抓起来。
- Use(使用)
- 基于“抓取”。例如抓取一把枪后,按下枪的扳机发射子弹则视为Use。
三、发起交互的对象(Interactor)
-
XR Direct Interactor脚本
- 为了让双手成为发起交互的对象,在LeftHand Controller和RightHand Controller下分别创建Direct Interactor子物体,并添加
XR Direct Interactor
脚本。
- 为了让双手成为发起交互的对象,在LeftHand Controller和RightHand Controller下分别创建Direct Interactor子物体,并添加
-
添加可交互区域
- XR Direct Interactor需要一个
Trigger
类型的碰撞体
作为可交互区域,且需和它挂载在同一游戏物体上。如在左手和右手的Direct Interactor上分别添加Sphere Collider
,调整Radius
并勾选Is Trigger
。当可交互对象进入该区域后,可进行交互。 ,并设置合适的参数。
- XR Direct Interactor需要一个
四、可交互的对象(Interactable)创建一个3d对象,比如立方体
-
添加刚体
- 因为手与物品的交互基于物理效果,所以要为可交互物体添加刚体。
-
XR Simple Interactable脚本
- 为方块添加
XR Simple Interactable
脚本,但是它没有自带抓取的功能。 - 物体有刚体后该脚本才生效,且
挂载脚本的物体还需一个碰撞体
。如果是创建的3d对象不需要额外加,如果是自定义模型的话,别忘了手动添加碰撞体
- 为方块添加
2.1、可交互事件
- 实现功能:
- 手触碰到方块时,方块颜色改变(模拟Hover,即悬停在可交互区域)。
- 触碰方块后按下手柄Grip键,方块颜色变成蓝色。
- 触碰方块后按住Grip键再按Trigger键,方块颜色变回红色。
- 实现方法:
- 使用
XR Simple Interactable
脚本中的Interactable Events
, - 其中
Hover Entered
对应开始悬停在可交互区域触发
的事件,可手动设置更改方块材质。 Select Entered
和Activated
事件分别对应上述第二、三个功能
。- 在Inspector面板中手动绑定事件,Select动作绑定“按下Grip键抓取键”,Activate动作绑定“按下Trigger键扳机键”。 Activate动作以Select动作为前提,Interactable Events中的所有事件以“与可交互对象发生交互”为前提。
- 使用
-
XR Grab Interactable脚本、和XR Simple Interactable脚本脚本类似,但是有抓取效果;所以需要移除刚刚的XR Simple Interactable脚本
-
Movement Type 移动类型
- Instantaneous:物体移动位置和姿态完全跟随手的移动,无延迟但无物理刚体效果,物体可穿过碰撞体且碰撞效果非物理。
- Kinematic:通过Kinematic Rigidbody移动,有延迟,移动中不受力和碰撞作用,但可对其他刚体施加物理效果。
- Velocity Tracking:通过设置刚体速度和角速度移动,有延迟,有刚体物理效果,可与碰撞体碰撞并对其他刚体产生力的效果。
-
Attach Transform 抓取点
XR Grab Interactable
脚本中有Attach Transform
变量可赋值作为抓取点,若未赋值,默认以物体position
为抓取点。如抓取枪时,抓取点应在枪柄;- 实现功能:可创建枪的子物体
Attach Point
并赋值给Attach Transform
,然后调整其Position位置
和Rotation
以优化抓取效果。但仅设置Attach Transform
会有穿模现象
,若要实现精细抓取,可参考相关教程。
-
-
代码实现Use功能(制作简易手枪)
-
核心脚本
- 创建
GunController
脚本挂载到枪的游戏物体上。获取XRGrabInteractable
中的activated
事件,通过AddListener
绑定FireBullet
函数。
- 创建
-
制作子弹预制体
- 创建子弹Prefab(需刚体和碰撞体),并将子弹Rigidbody的Collision Detection设为Continous Dynamic,防止高速运动时检测不到碰撞。
- 创建子弹Prefab(需刚体和碰撞体),并将子弹Rigidbody的Collision Detection设为Continous Dynamic,防止高速运动时检测不到碰撞。
-
制作子弹发射位置
- 创建枪的子物体Spawn Point作为子弹生成位置,其z轴箭头方向对应子弹发射方向,将子弹和Spawn Point赋给Gun Controller。
- 创建枪的子物体Spawn Point作为子弹生成位置,其z轴箭头方向对应子弹发射方向,将子弹和Spawn Point赋给Gun Controller。
-
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 组件
XRGrabInteractable grabbable = GetComponent<XRGrabInteractable>();
// 为 grabbable 的 activated 事件添加监听器,当该事件触发时调用 FireBullet 方法
grabbable.activated.AddListener(FireBullet);
}
private void FireBullet(ActivateEventArgs arg)
{
// 在 spawnPoint 的位置和旋转处实例化子弹预制体
GameObject spawnBullet = Instantiate(bullet, spawnPoint.position, spawnPoint.rotation);
// 设置实例化出的子弹的刚体速度,使其沿着 spawnPoint 的前方以 fireSpeed 的速度飞行
spawnBullet.GetComponent<Rigidbody>().velocity = spawnPoint.forward * fireSpeed;
// 在 5 秒后销毁这个子弹对象
Destroy(spawnBullet, 5);
}
}
- 优化一:左右手抓取(判断哪只手与物体交互)
- 方法一(不推荐)
- 在
GunController
脚本中,给XRGrabInteractable
的SelectEntered
事件绑定ChangeAttachTransform
函数,通过判断Interactor
是左手还是右手控制器来切换Attach Transform。
也就是创建两个抓取点,并进行监听ChangeAttachTransform
判断左右手、来重新设置抓取点
- 在
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()
{
// 在当前物体下查找名为"LeftHand Attach Point"的子物体,获取其变换组件
leftHandAttachPoint = transform.Find("LeftHand Attach Point");
// 在当前物体下查找名为"RightHand Attach Point"的子物体,获取其变换组件
rightHandAttachPoint = transform.Find("RightHand Attach Point");
// 获取当前物体上的 XRGrabInteractable 组件
grabbable = GetComponent<XRGrabInteractable>();
// 为可抓取交互组件的 selectEntered 事件添加监听器,当该事件触发时调用 ChangeAttachTransform 方法
grabbable.selectEntered.AddListener(ChangeAttachTransform);
// 为可抓取交互组件的 activated 事件添加监听器,当该事件触发时调用 FireBullet 方法
grabbable.activated.AddListener(FireBullet);
}
private void ChangeAttachTransform(SelectEnterEventArgs arg)
{
// 获取触发 selectEntered 事件的交互器的变换组件
Transform interactor = arg.interactorObject.transform;
// 如果交互器的父物体名称为"Left Controller",表示是左手控制器
if (interactor.transform.parent.name == "Left Controller")
{
// 将当前物体的抓取点设置为左手抓取点
grabbable.attachTransform = leftHandAttachPoint;
}
else if (interactor.transform.parent.name == "Right Controller")
{
// 如果交互器的父物体名称为"Right Controller",表示是右手控制器
// 将当前物体的抓取点设置为右手抓取点
grabbable.attachTransform = rightHandAttachPoint;
}
}
private void FireBullet(ActivateEventArgs arg)
{
// 在 spawnPoint 的位置和旋转处实例化子弹游戏对象
GameObject spawnBullet = Instantiate(bullet, spawnPoint.position, spawnPoint.rotation);
// 获取实例化出的子弹的刚体组件
Rigidbody bulletRigidbody = spawnBullet.GetComponent<Rigidbody>();
// 设置子弹的速度,使其沿着 spawnPoint 的前方以 fireSpeed 的速度飞行
bulletRigidbody.velocity = spawnPoint.forward * fireSpeed;
// 在 5 秒后销毁这个子弹对象
Destroy(spawnBullet, 5);
}
}
- 方法二减少耦合性
- 新建脚本
XRGrabInteractableTwoAttach
继承XRGrabInteractable
,重写OnSelectEntered
方法,根据Interactor的Tag
判断是左手还是右手,切换相应的Attach Transform
。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
// 自定义的可抓取交互类,继承自 XRGrabInteractable
public class XRGrabInteractableTwoAttach : XRGrabInteractable
{
// 左手的抓取点变换
public Transform leftAttachTransform;
// 右手的抓取点变换
public Transform rightAttachTransform;
// 重写 OnSelectEntered 方法,在选择进入时触发
protected override void OnSelectEntered(SelectEnterEventArgs args)
{
// 如果交互对象的标签是“Left Hand”(左手)
if (args.interactorObject.transform.CompareTag("Left Hand"))
{
// 将当前物体的抓取点设置为左手抓取点
attachTransform = leftAttachTransform;
}
// 如果交互对象的标签是“Right Hand”(右手)
else if (args.interactorObject.transform.CompareTag("Right Hand"))
{
// 将当前物体的抓取点设置为右手抓取点
attachTransform = rightAttachTransform;
}
// 调用基类的 OnSelectEntered 方法
base.OnSelectEntered(args);
}
}
- 将枪上的
XRGrabInteractable
抓取脚本替换为XRGrabInteractableTwoAttach
,并在编辑器中为左右手Attach Transform
赋值并给Direct Interactor
添加Tag
。
- 第一次抓取或第一次切换抓取位置错误解决方法
- 方法一
- 在可抓取物体Gun被抓取物体上添加XRSingleGrabFreeTransformer
脚本,游戏运行时会自动添加到XRGrabInteractableTwoAttach
脚本。
方法二
- 重写刚刚新建的XRGrabInteractableTwoAttach
脚本的GetAttachTransform
方法,而不是OnSelectEntered
方法。根据Interactor
的Tag
返回相应的Attach Transform
,若Tag不匹配则返回基类的GetAttachTransform
结果。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
public class XRGrabInteractableTwoAttach : XRGrabInteractable
{
public Transform leftAttachTransform;
public Transform rightAttachTransform;
public override Transform GetAttachTransform(IXRInteractor interactor)
{
Transform i_attachTransform = null;
if (interactor.transform.CompareTag("Left Hand"))
{
i_attachTransform = leftAttachTransform;
}
if (interactor.transform.CompareTag("Right Hand"))
{
i_attachTransform = rightAttachTransform;
}
return i_attachTransform != null ? i_attachTransform : base.GetAttachTransform(interactor);
}
}
因为之前学的射线交互层级是Everything层级比较高和物体交互层级有重复,所以也能实现射线抓取;然后将与传送有关的 XR Ray Interactor 和 Teleport Area 的 Interaction Layer Mask 改成 Teleport,就可以避免出现这类问题
-
其他功能一:将与物体接触的地方作为抓取点(Dynamic Attach),也就是碰哪里抓哪里,没有固定抓取点
- 创建细长Cube作为测试物体,添加碰撞体、刚体和XR Grab Interactable脚本,勾选Use Dynamic Attach后,可根据需求设置Match Position、Match Rotation、Snap To Collider等选项,实现手抓在物体接触部位的效果。
Match Position(匹配位置):当启用相关功能时,确保被抓取物体的位置与抓取点的位置相匹配。例如在使用特定的抓取方式时,可使被抓取物体在被抓取瞬间其位置与抓取点重合,以实现更真实的抓取效果。
Match Rotation(匹配旋转):类似 “匹配位置”,当启用此选项时,被抓取物体的旋转会与抓取点的旋转相匹配。这样可以确保被抓取物体在抓取过程中以合适的角度呈现,增强真实感和交互性。
Snap To Collider(吸附到碰撞体):当开启这个功能后,抓取操作会使被抓取物体吸附到抓取点所在的碰撞体上。这可以使抓取更加精准,并且在抓取过程中物体与抓取点的连接更加紧密,避免出现不合理的位置偏差。
五、XR Tint Interactable Visual脚本
- 功能:可挂载到可交互对象上,当Interactor悬停(Hover)或选中(Select动作触发)可交互对象时,能暂时改变其颜色。
.2. 设置:调整Tint Color设置颜色,勾选Tint On Hover在Hover时改变颜色,勾选Tint On Selection在Select时改变颜色。注意要把使用的XR Grab Interactable放置到最上层。
六、取消身体和可抓取物体的物理碰撞
-
设置Layer:将XR Origin的Layer设为Player(仅设置Character Controller所在物体Layer,子物体选No),将所有可抓取物体Layer设为Interactable。
-
配置Physics:打开Unity编辑器上方菜单栏的Edit编辑 -> Project Settings项目设置-> Physics物理,找到Layer Collision Matrix图层碰撞器,将Player和Interactable的交叉点取消勾选,避免身体与可抓取物体发生物理碰撞。
七、XR Interaction Group
-
功能:XR Interaction Toolkit 2.3新组件,可管理多个Interactor。当其中一个Interactor生效时,Group内其他Interactor会暂时失效。
-
应用示例:如在Left/RightHand Controller物体上添加XR Interaction Group组件,将Direct Interactor物体和UI Ray Interactor物体拖到Group中,可实现在抓取物体时让UI射线暂时失效。
八、XR Direct Interactor脚本中的Select Action Trigger
- 参数说明
- Toggle:以抓取为例,选择Toggle后,靠近物体按下手柄抓取键,物体会被抓在手上,松开抓取键物体仍在手上,下次按下抓取键才会释放。
- Sticky:按下手柄抓取键物体被抓手上,松开抓取键物体仍在手上,下次按下并松开抓取键物体才会释放。
- State和State Change的区别(在可抓取物体Select Mode选择Single时)
- State:可能出现一只手无法接管另一只手抓取权的情况,只有先松开当前抓取手的抓取键才会进行切换抓取。
- State Change:可以随意切换抓取,推荐在抓取功能上使用State Change。