VR中的空间感知与交互
在虚拟现实(VR)游戏中,空间感知与交互是核心功能之一,它们直接影响玩家的沉浸感和游戏体验。本节将详细介绍如何在Unity引擎中实现这些功能,包括空间定位、手部追踪、物体抓取和释放、以及碰撞检测等。我们将通过具体的代码示例来帮助你更好地理解和实现这些功能。
空间定位(Spatial Localization)
空间定位是指VR系统能够准确地跟踪用户在虚拟环境中的位置和方向。在Unity中,这通常通过外部追踪设备(如SteamVR或Oculus追踪系统)实现。我们将介绍如何在Unity中设置和使用这些追踪设备。
设置追踪设备
-
安装必要的插件:
-
首先,确保你的Unity项目中安装了与你使用的VR设备对应的插件。例如,如果你使用的是HTC Vive或Valve Index,你需要安装SteamVR插件。
-
打开Unity Hub,创建一个新的项目或打开现有的项目。
-
在Unity编辑器中,进入
Window
->Package Manager
,搜索并安装XR Plugin Management
和XR Interaction Toolkit
。
-
-
配置XR设置:
-
在Unity编辑器中,进入
Edit
->Project Settings
->XR Plugin Management
。 -
选择
Standalone
(Windows、Mac或Linux)或Android
(如果目标平台是移动设备)。 -
启用相应的VR SDK,例如SteamVR或Oculus。
-
-
创建追踪点:
-
在
Hierarchy
窗口中,创建一个新的空对象,命名为TrackingOrigin
。 -
将
XR Rig
组件添加到TrackingOrigin
对象上。这个组件会自动设置追踪点,并帮助你管理头显和手柄的位置。
-
// 创建追踪点的脚本示例
using UnityEngine;
using UnityEngine.XR;
public class TrackingSetup : MonoBehaviour
{
// 在Awake方法中初始化追踪点
void Awake()
{
// 创建追踪点
GameObject trackingOrigin = new GameObject("TrackingOrigin");
trackingOrigin.transform.position = Vector3.zero;
trackingOrigin.transform.rotation = Quaternion.identity;
// 添加XR Rig组件
XR Rig rig = trackingOrigin.AddComponent<XR Rig>();
rig.trackingOriginMode = TrackingOriginModeFlags.Floor;
}
}
获取头显和手柄的位置和方向
-
头显的位置和方向:
- 使用
InputTracking.GetLocalPosition
和InputTracking.GetLocalRotation
方法获取头显的位置和方向。
- 使用
// 获取头显位置和方向的脚本示例
using UnityEngine;
using UnityEngine.XR;
public class HeadsetPosition : MonoBehaviour
{
void Update()
{
// 获取头显的位置
Vector3 headsetPosition = InputTracking.GetLocalPosition(XRNode.Head);
// 获取头显的方向
Quaternion headsetRotation = InputTracking.GetLocalRotation(XRNode.Head);
// 输出头显的位置和方向
Debug.Log("Headset Position: " + headsetPosition);
Debug.Log("Headset Rotation: " + headsetRotation);
}
}
-
手柄的位置和方向:
- 使用
InputTracking.GetLocalPosition
和InputTracking.GetLocalRotation
方法获取手柄的位置和方向。
- 使用
// 获取手柄位置和方向的脚本示例
using UnityEngine;
using UnityEngine.XR;
public class ControllerPosition : MonoBehaviour
{
void Update()
{
// 获取左控制器的位置
Vector3 leftControllerPosition = InputTracking.GetLocalPosition(XRNode.LeftHand);
// 获取左控制器的方向
Quaternion leftControllerRotation = InputTracking.GetLocalRotation(XRNode.LeftHand);
// 获取右控制器的位置
Vector3 rightControllerPosition = InputTracking.GetLocalPosition(XRNode.RightHand);
// 获取右控制器的方向
Quaternion rightControllerRotation = InputTracking.GetLocalRotation(XRNode.RightHand);
// 输出手柄的位置和方向
Debug.Log("Left Controller Position: " + leftControllerPosition);
Debug.Log("Left Controller Rotation: " + leftControllerRotation);
Debug.Log("Right Controller Position: " + rightControllerPosition);
Debug.Log("Right Controller Rotation: " + rightControllerRotation);
}
}
手部追踪(Hand Tracking)
手部追踪是指VR系统能够准确地跟踪用户的手部动作,并在虚拟环境中进行相应的显示和交互。Unity支持多种手部追踪方案,包括使用手柄和无手柄的裸手追踪。
使用手柄的手部追踪
-
设置输入映射:
-
在Unity编辑器中,进入
Edit
->Project Settings
->Input Manager
。 -
添加新的输入轴,用于手柄的触发器和按钮输入。
-
-
创建手柄控制器模型:
-
在
Hierarchy
窗口中,创建一个新的空对象,命名为LeftController
和RightController
。 -
将手柄模型(通常是FBX文件)拖放到这些控制器对象上。
-
添加
XR Controller Model
组件,并配置相应的手柄模型。
-
-
获取手柄输入:
- 使用
Input.GetAxis
和Input.GetButton
方法获取手柄的触发器和按钮输入。
- 使用
// 获取手柄输入的脚本示例
using UnityEngine;
public class ControllerInput : MonoBehaviour
{
// 左手柄的触发器输入
public float leftTriggerValue;
// 右手柄的触发器输入
public float rightTriggerValue;
void Update()
{
// 获取左控制器的触发器输入
leftTriggerValue = Input.GetAxis("LeftTrigger");
// 获取右控制器的触发器输入
rightTriggerValue = Input.GetAxis("RightTrigger");
// 检查左控制器的按钮输入
if (Input.GetButton("LeftGrip"))
{
Debug.Log("Left Grip Button Pressed");
}
// 检查右控制器的按钮输入
if (Input.GetButton("RightGrip"))
{
Debug.Log("Right Grip Button Pressed");
}
}
}
无手柄的裸手追踪
-
安装手部追踪插件:
-
例如,如果你使用的是Oculus设备,需要安装Oculus Integration插件,并启用手部追踪功能。
-
在Unity编辑器中,进入
Window
->Package Manager
,搜索并安装Oculus Integration
。
-
-
配置手部追踪:
- 在
Oculus
菜单中,选择Configure
->Project Settings
,启用手部追踪功能。
- 在
-
获取手部追踪数据:
- 使用
OVRInput.GetLocalControllerPose
方法获取手部的位置和方向。
- 使用
// 获取无手柄手部追踪数据的脚本示例
using UnityEngine;
using Oculus;
public class HandTracking : MonoBehaviour
{
void Update()
{
// 获取左控制器的位置和方向
OVRPose leftHandPose = OVRInput.GetLocalControllerPose(OVRInput.Controller.LTouch);
Vector3 leftHandPosition = leftHandPose.position;
Quaternion leftHandRotation = leftHandPose.orientation;
// 获取右控制器的位置和方向
OVRPose rightHandPose = OVRInput.GetLocalControllerPose(OVRInput.Controller.RTouch);
Vector3 rightHandPosition = rightHandPose.position;
Quaternion rightHandRotation = rightHandPose.orientation;
// 输出手部的位置和方向
Debug.Log("Left Hand Position: " + leftHandPosition);
Debug.Log("Left Hand Rotation: " + leftHandRotation);
Debug.Log("Right Hand Position: " + rightHandPosition);
Debug.Log("Right Hand Rotation: " + rightHandRotation);
}
}
物体抓取和释放(Object Grabbing and Releasing)
在VR游戏中,物体抓取和释放是常见的交互方式。我们将介绍如何在Unity中实现这些功能,包括使用手柄和手部追踪。
使用手柄抓取和释放物体
-
创建抓取点:
-
在
Hierarchy
窗口中,创建一个新的空对象,命名为GrabPoint
,并将其作为手柄控制器的子对象。 -
添加
Collider
组件,用于检测物体碰撞。
-
-
编写抓取和释放脚本:
-
使用
OnCollisionEnter
和OnCollisionExit
方法检测物体碰撞。 -
使用
AddForce
或AddTorque
方法模拟抓取和释放物体的物理效果。
-
// 抓取和释放物体的脚本示例
using UnityEngine;
public class ObjectGrabbing : MonoBehaviour
{
// 抓取的物体
public GameObject grabbedObject;
// 抓取点的刚体组件
private Rigidbody grabPointRigidbody;
// 抓取点的 Collider 组件
private Collider grabPointCollider;
void Start()
{
// 获取抓取点的刚体和 Collider 组件
grabPointRigidbody = GetComponent<Rigidbody>();
grabPointCollider = GetComponent<Collider>();
}
void Update()
{
// 检查触发器输入
if (Input.GetAxis("LeftTrigger") > 0.5f && grabbedObject == null)
{
// 尝试抓取物体
GrabObject();
}
if (Input.GetAxis("LeftTrigger") < 0.1f && grabbedObject != null)
{
// 释放物体
ReleaseObject();
}
}
void GrabObject()
{
// 检测碰撞的物体
Collider[] colliders = Physics.OverlapSphere(transform.position, 0.1f);
foreach (Collider collider in colliders)
{
// 检查物体是否有 Rigidbody 组件
Rigidbody rb = collider.GetComponent<Rigidbody>();
if (rb != null)
{
// 抓取物体
grabbedObject = collider.gameObject;
rb.isKinematic = true;
rb.transform.SetParent(transform);
break;
}
}
}
void ReleaseObject()
{
if (grabbedObject != null)
{
// 释放物体
Rigidbody rb = grabbedObject.GetComponent<Rigidbody>();
rb.isKinematic = false;
rb.transform.SetParent(null);
grabbedObject = null;
}
}
}
使用手部追踪抓取和释放物体
-
检测手部与物体的碰撞:
- 使用
LineCast
或SphereCast
方法检测手部与物体的碰撞。
- 使用
-
编写抓取和释放脚本:
-
使用
OnHandOverlaps
方法检测手部与物体的重叠。 -
使用
OnHandPose
方法检测手部姿势,以确定是否进行抓取或释放操作。
-
// 抓取和释放物体的脚本示例(手部追踪)
using UnityEngine;
using Oculus;
public class HandObjectGrabbing : MonoBehaviour
{
// 抓取的物体
public GameObject grabbedObject;
// 抓取点的刚体组件
private Rigidbody grabPointRigidbody;
// 抓取点的 Collider 组件
private Collider grabPointCollider;
void Start()
{
// 获取抓取点的刚体和 Collider 组件
grabPointRigidbody = GetComponent<Rigidbody>();
grabPointCollider = GetComponent<Collider>();
}
void Update()
{
// 检查手部姿势
OVRInput.HandPose leftHandPose = OVRInput.GetActiveControllerPose(OVRInput.Controller.LTouch).handPose;
OVRInput.HandPose rightHandPose = OVRInput.GetActiveControllerPose(OVRInput.Controller.RTouch).handPose;
if (leftHandPose == OVRInput.HandPose.Grab && grabbedObject == null)
{
// 尝试抓取物体
GrabObject(OVRInput.GetLocalControllerPose(OVRInput.Controller.LTouch).position, OVRInput.GetLocalControllerPose(OVRInput.Controller.LTouch).orientation);
}
if (leftHandPose == OVRInput.HandPose.Open && grabbedObject != null)
{
// 释放物体
ReleaseObject();
}
if (rightHandPose == OVRInput.HandPose.Grab && grabbedObject == null)
{
// 尝试抓取物体
GrabObject(OVRInput.GetLocalControllerPose(OVRInput.Controller.RTouch).position, OVRInput.GetLocalControllerPose(OVRInput.Controller.RTouch).orientation);
}
if (rightHandPose == OVRInput.HandPose.Open && grabbedObject != null)
{
// 释放物体
ReleaseObject();
}
}
void GrabObject(Vector3 handPosition, Quaternion handRotation)
{
// 检测手部与物体的碰撞
RaycastHit hit;
if (Physics.SphereCast(handPosition, 0.05f, handRotation * Vector3.forward, out hit, 0.1f))
{
// 检查物体是否有 Rigidbody 组件
Rigidbody rb = hit.collider.GetComponent<Rigidbody>();
if (rb != null)
{
// 抓取物体
grabbedObject = hit.collider.gameObject;
rb.isKinematic = true;
rb.transform.SetParent(transform);
}
}
}
void ReleaseObject()
{
if (grabbedObject != null)
{
// 释放物体
Rigidbody rb = grabbedObject.GetComponent<Rigidbody>();
rb.isKinematic = false;
rb.transform.SetParent(null);
grabbedObject = null;
}
}
}
碰撞检测(Collision Detection)
在VR游戏中,碰撞检测是确保物体之间正确互动的关键。我们将介绍如何在Unity中实现基本的碰撞检测。
基本碰撞检测
-
创建物体:
-
在
Hierarchy
窗口中,创建两个物体,例如一个立方体和一个球体。 -
为每个物体添加
Collider
组件。
-
-
编写碰撞检测脚本:
- 使用
OnCollisionEnter
、OnCollisionStay
和OnCollisionExit
方法检测物体之间的碰撞。
- 使用
// 基本碰撞检测的脚本示例
using UnityEngine;
public class CollisionDetection : MonoBehaviour
{
void OnCollisionEnter(Collision collision)
{
// 碰撞开始时的处理
Debug.Log("Collision started with: " + collision.gameObject.name);
}
void OnCollisionStay(Collision collision)
{
// 碰撞持续时的处理
Debug.Log("Collision staying with: " + collision.gameObject.name);
}
void OnCollisionExit(Collision collision)
{
// 碰撞结束时的处理
Debug.Log("Collision ended with: " + collision.gameObject.name);
}
}
触发器碰撞检测
-
设置触发器:
-
将
Collider
组件的Is Trigger
属性设置为true
。 -
为触发器对象添加
Rigidbody
组件,并将其Is Kinematic
属性设置为true
。
-
-
编写触发器碰撞检测脚本:
- 使用
OnTriggerEnter
、OnTriggerStay
和OnTriggerExit
方法检测触发器碰撞。
- 使用
// 触发器碰撞检测的脚本示例
using UnityEngine;
public class TriggerCollisionDetection : MonoBehaviour
{
void OnTriggerEnter(Collider other)
{
// 触发器碰撞开始时的处理
Debug.Log("Trigger collision started with: " + other.gameObject.name);
}
void OnTriggerStay(Collider other)
{
// 触发器碰撞持续时的处理
Debug.Log("Trigger collision staying with: " + other.gameObject.name);
}
void OnTriggerExit(Collider other)
{
// 触发器碰撞结束时的处理
Debug.Log("Trigger collision ended with: " + other.gameObject.name);
}
}
复杂碰撞检测
-
使用复合碰撞器:
-
为复杂的物体添加多个
Collider
组件,形成复合碰撞器。 -
例如,一个机器人模型可以由多个网格碰撞器组成。
-
-
编写复杂碰撞检测脚本:
-
使用
Physics.Raycast
方法检测射线与物体的碰撞。 -
使用
Physics.SphereCast
方法检测球形射线与物体的碰撞。
-
// 复杂碰撞检测的脚本示例
using UnityEngine;
public class ComplexCollisionDetection : MonoBehaviour
{
// 射线的长度
public float rayLength = 10f;
// 射线的源点
public Transform raySource;
void Update()
{
// 射线检测
Ray ray = new Ray(raySource.position, raySource.forward);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, rayLength))
{
// 检测到射线碰撞
Debug.Log("Ray hit: " + hit.collider.gameObject.name);
}
// 球形射线检测
Vector3 spherePosition = raySource.position + raySource.forward * 0.5f;
if (Physics.SphereCast(spherePosition, 0.1f, raySource.forward, out hit, 5f))
{
// 检测到球形射线碰撞
Debug.Log("Sphere ray hit: " + hit.collider.gameObject.name);
}
}
}
交互示例(Interaction Examples)
为了更好地理解空间感知和交互的实现,我们将通过几个具体的交互示例来展示如何在Unity中实现这些功能。
示例1:开门和关门
-
创建门模型:
-
在
Hierarchy
窗口中,创建一个门模型,例如一个旋转门。 -
为门模型添加
Hinge Joint
组件,以实现门的旋转。
-
-
编写门的交互脚本:
- 使用手柄的触发器和按钮输入来控制门的开合。
// 门的交互脚本示例
using UnityEngine;
public class DoorInteraction : MonoBehaviour
{
// 门的 Hinge Joint 组件
public HingeJoint hingeJoint;
// 门的打开角度
public float openAngle = 90f;
// 门的关闭角度
public float closeAngle = 0f;
// 门的旋转速度
public float rotationSpeed = 5f;
// 门的目标角度
private float targetAngle;
void Start()
{
// 初始化门的目标角度为关闭状态
targetAngle = closeAngle;
}
void Update()
{
// 检查触发器输入
if (Input.GetAxis("LeftTrigger") > 0.5f)
{
// 打开门
targetAngle = openAngle;
}
if (Input.GetAxis("LeftTrigger") < 0.1f)
{
// 关闭门
targetAngle = closeAngle;
}
// 逐步旋转门到目标角度
RotateDoor();
}
void RotateDoor()
{
// 获取门的当前角度
float currentAngle = hingeJoint.angle;
// 计算角度差
float angleDifference = targetAngle - currentAngle;
// 如果角度差大于0.1度,继续旋转
if (Mathf.Abs(angleDifference) > 0.1f)
{
// 计算旋转方向
float rotationDirection = angleDifference > 0 ? 1 : -1;
// 逐步旋转门
hingeJoint.useMotor = true;
JointMotor motor = hingeJoint.motor;
motor.targetVelocity = rotationSpeed * rotationDirection;
hingeJoint.motor = motor;
}
else
{
// 停止旋转
hingeJoint.useMotor = false;
JointMotor motor = hingeJoint.motor;
motor.targetVelocity = 0;
hingeJoint.motor = motor;
}
}
}
示例2:物体拾取和放置
-
创建可拾取的物体:
-
在
Hierarchy
窗口中,创建一个可拾取的物体,例如一个立方体。 -
为物体添加
Rigidbody
和Collider
组件。
-
-
编写物体拾取和放置脚本:
- 使用手柄的触发器和按钮输入来控制物体的拾取和放置。
// 物体拾取和放置的脚本示例
using UnityEngine;
public class ObjectPickupAndPlace : MonoBehaviour
{
// 抓取的物体
public GameObject grabbedObject;
// 抓取点的刚体组件
private Rigidbody grabPointRigidbody;
// 抓取点的 Collider 组件
private Collider grabPointCollider;
void Start()
{
// 获取抓取点的刚体和 Collider 组件
grabPointRigidbody = GetComponent<Rigidbody>();
grabPointCollider = GetComponent<Collider>();
}
void Update()
{
// 检查触发器输入
if (Input.GetAxis("LeftTrigger") > 0.5f && grabbedObject == null)
{
// 尝试抓取物体
GrabObject();
}
if (Input.GetAxis("LeftTrigger") < 0.1f && grabbedObject != null)
{
// 释放物体
ReleaseObject();
}
}
void GrabObject()
{
// 检测碰撞的物体
Collider[] colliders = Physics.OverlapSphere(transform.position, 0.1f);
foreach (Collider collider in colliders)
{
// 检查物体是否有 Rigidbody 组件
Rigidbody rb = collider.GetComponent<Rigidbody>();
if (rb != null)
{
// 抓取物体
grabbedObject = collider.gameObject;
rb.isKinematic = true;
rb.transform.SetParent(transform);
break;
}
}
}
void ReleaseObject()
{
if (grabbedObject != null)
{
// 释放物体
Rigidbody rb = grabbedObject.GetComponent<Rigidbody>();
rb.isKinematic = false;
rb.transform.SetParent(null);
grabbedObject = null;
}
}
}
示例3:虚拟按钮交互
-
创建虚拟按钮模型:
-
在
Hierarchy
窗口中,创建一个虚拟按钮模型,例如一个平面按钮。 -
为按钮模型添加
Collider
组件,并将其设置为触发器。
-
-
编写虚拟按钮交互脚本:
- 使用手柄或手部追踪的输入来检测按钮的按下和释放。
// 虚拟按钮交互脚本示例
using UnityEngine;
public class VirtualButton : MonoBehaviour
{
// 按钮按下时的处理
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Hand"))
{
Debug.Log("Button pressed");
// 执行按钮按下时的逻辑,例如播放音效、触发事件等
}
}
// 按钮释放时的处理
void OnTriggerExit(Collider other)
{
if (other.CompareTag("Hand"))
{
Debug.Log("Button released");
// 执行按钮释放时的逻辑,例如停止音效、取消事件等
}
}
}
示例4:虚拟滑块交互
-
创建虚拟滑块模型:
-
在
Hierarchy
窗口中,创建一个虚拟滑块模型,例如一个滑动条。 -
为滑块模型添加
Collider
组件,并将其设置为触发器。 -
为滑动条的背景和滑块添加相应的模型。
-
-
编写虚拟滑块交互脚本:
- 使用手柄或手部追踪的输入来检测滑块的滑动。
// 虚拟滑块交互脚本示例
using UnityEngine;
public class VirtualSlider : MonoBehaviour
{
// 滑块的移动范围
public Vector3 minPosition;
public Vector3 maxPosition;
// 滑块当前的位置
private Vector3 sliderPosition;
// 滑块是否被抓住
private bool isGrabbed = false;
void Start()
{
// 初始化滑块的位置
sliderPosition = transform.position;
}
void Update()
{
// 检查触发器输入
if (Input.GetAxis("LeftTrigger") > 0.5f)
{
// 尝试抓取滑块
GrabSlider();
}
if (Input.GetAxis("LeftTrigger") < 0.1f)
{
// 释放滑块
ReleaseSlider();
}
// 如果滑块被抓住,更新滑块的位置
if (isGrabbed)
{
MoveSlider();
}
}
void GrabSlider()
{
// 检测碰撞的物体
Collider[] colliders = Physics.OverlapSphere(transform.position, 0.1f);
foreach (Collider collider in colliders)
{
if (collider.CompareTag("Hand"))
{
// 抓取滑块
isGrabbed = true;
break;
}
}
}
void ReleaseSlider()
{
if (isGrabbed)
{
// 释放滑块
isGrabbed = false;
}
}
void MoveSlider()
{
// 获取手柄的位置
Vector3 handPosition = InputTracking.GetLocalPosition(XRNode.LeftHand);
// 计算滑块的新位置
sliderPosition = new Vector3(
Mathf.Clamp(handPosition.x, minPosition.x, maxPosition.x),
Mathf.Clamp(handPosition.y, minPosition.y, maxPosition.y),
Mathf.Clamp(handPosition.z, minPosition.z, maxPosition.z)
);
// 更新滑块的位置
transform.position = sliderPosition;
}
}
总结
通过上述示例,我们展示了如何在Unity引擎中实现VR游戏中的空间感知与交互功能。这些功能包括空间定位、手部追踪、物体抓取和释放、以及碰撞检测。每种功能的实现都依赖于Unity中的XR插件和物理引擎,通过合理的配置和脚本编写,可以为玩家提供更加沉浸和真实的交互体验。
进一步优化
-
平滑移动:
- 使用插值(lerp)方法平滑移动物体,避免突兀的跳跃。
-
物理效果:
- 为抓取和释放物体添加物理效果,例如碰撞声音、振动反馈等,增强交互的真实感。
-
UI交互:
- 为虚拟按钮和滑块添加动画和音效,提高用户的交互体验。
-
多平台支持:
- 确保代码在不同平台(如SteamVR、Oculus、Windows Mixed Reality)上都能正常工作,进行必要的适配和优化。
希望这些示例和代码能帮助你在Unity中开发出更加精彩的VR游戏。如果你有任何问题或需要进一步的帮助,请随时查阅Unity官方文档或社区资源。