You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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数据,未抓取物体执行自定义物理逻辑后同步位置

使用注意事项

  1. 确保项目已导入XR Interaction Toolkit和Oculus XR Plugin包
  2. 在Unity编辑器中,将左右手控制器组件拖入脚本的leftController和rightController字段
  3. 可根据需求调整抓取判定距离(0.5f)、排斥力范围(2f)等参数

内容的提问来源于stack exchange,提问作者Omar Alhussani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 02:57:08