基于Unity XR Toolkit实现生成物体的Oculus控制器交互功能
Oculus控制器多物体抓取与位置追踪实现
需求说明
- 基于现有节点物体生成脚本,实现Oculus控制器对物体的抓取、操作功能
- 实时检测多物体抓取状态,同步追踪物体位置与自定义物理节点数据
改造后完整代码
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.XR; using UnityEngine.XR.Interaction.Toolkit; // 自定义节点数据类 class Node { public float x; public float y; public float z; public float mass = 1; public float spring_constant = 1; public Node[] connections; // 关联对应的游戏物体 public GameObject linkedObject; // 是否被抓取标记 public bool isGrabbed; public Node(float x, float y, float z, Node[] connections) { this.x = x; this.y = y; this.z = z; this.connections = connections; isGrabbed = false; } } public class SpawnGraph : MonoBehaviour { private Node[] nodes = new Node[10]; // 存储控制器与抓取物体的映射关系 private Dictionary<XRController, Node> controllerGrabbedNodes = new Dictionary<XRController, Node>(); // 引用左右手控制器 public XRController leftController; public XRController rightController; // 抓取触发按键(Oculus默认扳机键) private InputHelpers.Button grabButton = InputHelpers.Button.Trigger; void Start() { // 生成10个随机位置的节点 for (int i = 0; i < 10; i++) { nodes[i] = new Node(Random.Range(0f, 10f), Random.Range(0f, 10f), Random.Range(0f, 10f), null); } // 生成对应球体物体并配置物理组件 for (int i = 0; i < 10; i++) { GameObject nodeObj = GameObject.CreatePrimitive(PrimitiveType.Sphere); nodeObj.transform.position = new Vector3(nodes[i].x, nodes[i].y, nodes[i].z); // 添加刚体组件,关闭重力以适配自定义物理 Rigidbody rb = nodeObj.AddComponent<Rigidbody>(); rb.useGravity = false; rb.mass = nodes[i].mass; // 绑定节点与物理物体 nodes[i].linkedObject = nodeObj; } } void Update() { // 处理左右手控制器的抓取/释放逻辑 HandleControllerGrab(leftController); HandleControllerGrab(rightController); // 同步被抓取物体的位置到节点数据 SyncGrabbedNodePositions(); // 执行自定义物理逻辑(仅作用于未被抓取的节点) ApplyCustomPhysics(); } // 单个控制器的抓取逻辑处理 void HandleControllerGrab(XRController controller) { if (controller == null) return; // 检测抓取按键状态 if (InputHelpers.IsPressed(controller.inputDevice, grabButton, out bool isPressed)) { if (isPressed) { // 未抓取物体时,检测附近可抓取节点 if (!controllerGrabbedNodes.ContainsKey(controller)) { Node closestNode = FindClosestNodeToController(controller); if (closestNode != null && !closestNode.isGrabbed) { controllerGrabbedNodes.Add(controller, closestNode); closestNode.isGrabbed = true; // 禁用刚体动力学,避免自定义物理与控制器操作冲突 closestNode.linkedObject.GetComponent<Rigidbody>().isKinematic = true; } } } else { // 释放已抓取的物体 if (controllerGrabbedNodes.TryGetValue(controller, out Node grabbedNode)) { controllerGrabbedNodes.Remove(controller); grabbedNode.isGrabbed = false; // 恢复刚体物理属性 grabbedNode.linkedObject.GetComponent<Rigidbody>().isKinematic = false; } } } } // 查找控制器附近最近的未抓取节点 Node FindClosestNodeToController(XRController controller) { if (!controller.inputDevice.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 controllerPos)) return null; Node closestNode = null; float minDistance = float.MaxValue; foreach (Node node in nodes) { if (node.isGrabbed) continue; float distance = Vector3.Distance(controllerPos, node.linkedObject.transform.position); // 0.5米范围内判定为可抓取 if (distance < minDistance && distance < 0.5f) { minDistance = distance; closestNode = node; } } return closestNode; } // 同步被抓取物体的位置到Node数据 void SyncGrabbedNodePositions() { foreach (var pair in controllerGrabbedNodes) { XRController controller = pair.Key; Node node = pair.Value; if (controller.inputDevice.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 controllerPos)) { // 更新物体位置到控制器位置 node.linkedObject.transform.position = controllerPos; // 同步Node数据 node.x = controllerPos.x; node.y = controllerPos.y; node.z = controllerPos.z; } } } // 自定义物理逻辑(节点间排斥力示例) void ApplyCustomPhysics() { foreach (Node node in nodes) { if (node.isGrabbed) continue; Vector3 repulsionForce = Vector3.zero; foreach (Node otherNode in nodes) { if (node == otherNode || otherNode.isGrabbed) continue; Vector3 direction = node.linkedObject.transform.position - otherNode.linkedObject.transform.position; float distance = direction.magnitude; // 2米范围内产生排斥力 if (distance < 2f) { repulsionForce += direction.normalized * (1 / distance) * node.spring_constant; } } // 给刚体施加排斥力 node.linkedObject.GetComponent<Rigidbody>().AddForce(repulsionForce); // 同步Node数据到物体位置 node.x = node.linkedObject.transform.position.x; node.y = node.linkedObject.transform.position.y; node.z = node.linkedObject.transform.position.z; } } }
关键实现说明
- 节点与物体绑定:给
Node类添加linkedObject和isGrabbed字段,实现数据层与物理物体的关联 - 控制器交互:通过
XRController和InputHelpers检测Oculus控制器的扳机输入,处理抓取/释放流程 - 抓取状态管理:用字典记录控制器与抓取节点的映射,避免同一物体被多个控制器同时抓取
- 物理冲突处理:抓取时禁用物体刚体的动力学属性,释放后恢复,确保自定义物理与控制器操作互不干扰
- 位置追踪:实时同步被抓取物体的控制器位置到
Node数据,未抓取物体执行自定义物理逻辑后同步位置
使用注意事项
- 确保项目已导入XR Interaction Toolkit和Oculus XR Plugin包
- 在Unity编辑器中,将左右手控制器组件拖入脚本的
leftController和rightController字段 - 可根据需求调整抓取判定距离(0.5f)、排斥力范围(2f)等参数
内容的提问来源于stack exchange,提问作者Omar Alhussani
相关产品推荐
相关产品推荐

