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

Unity XRGrabInteractable位置同步异常:原因分析与修复方案

问题分析与修复方案

问题原因

  • 双重位置控制冲突:你同时通过自定义position变量维护节点位置,又在Node.Update()里强制将XRGrabInteractable的位置同步到这个变量。但XRGrabInteractable在被抓取时会自主更新挂载对象的位置,两者的更新逻辑互相干扰,短时间内就会出现位置漂移分离。
  • 抓取位置同步不彻底:OnGrab中仅在抓取瞬间同步一次位置,但XR控制器在抓取过程中会持续移动交互对象,而自定义position没有实时跟随更新,后续Node.Update()又会把交互对象位置拉回过时的position值,加剧不同步问题。
  • 连接重复更新冗余:每个Connection被两个关联Node同时添加到各自的connections列表,导致同一条线被重复调用Update(),虽不直接引发位置问题,但会造成性能浪费。

修复方法

核心思路:让XR系统主导位置控制

放弃自定义维护position变量,直接通过球体的transform.position完成所有位置操作,消除双重控制冲突;同时在抓取时禁用节点自主移动逻辑,避免与XR控制器的移动逻辑冲突。

修改后的完整代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class Connection
{
    public Node a;
    public Node b;
    public LineRenderer line;

    public Connection(Node a, Node b)
    {
        this.a = a;
        this.b = b;
        GameObject lineObject = new GameObject();
        line = lineObject.AddComponent<LineRenderer>();
        line.startWidth = 0.1f;
        line.endWidth = 0.1f;
        UpdateLinePositions();
    }

    public void UpdateLinePositions()
    {
        line.SetPosition(0, a.sphere.transform.position);
        line.SetPosition(1, b.sphere.transform.position);
    }
}

public class Node
{
    public Vector3 color = new Vector3(1, 1, 1);
    public bool grabbed = false;

    private XRGrabInteractable grabInteractable;
    public GameObject sphere;
    public float q = 0.09f;  // 节点电荷
    public float k = 6.67f;  // 库仑常数
    public float spring_constant = 0.5f;
    public List<Connection> connections = new List<Connection>();

    public Node(Vector3 pos, Connection[] connections = null)
    {
        color = new Vector3(Random.Range(0, 1), Random.Range(0, 1), Random.Range(0, 1));
        // 创建球体
        GameObject sphereObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        sphereObject.transform.position = pos;
        sphereObject.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
        sphereObject.GetComponent<Renderer>().material.color = new Color(color.x, color.y, color.z);
        // 添加交互组件
        grabInteractable = sphereObject.AddComponent<XRGrabInteractable>();
        grabInteractable.onSelectEntered.AddListener(OnGrab);
        grabInteractable.onSelectExited.AddListener(OnRelease);

        this.sphere = sphereObject;
        if (connections != null)
        {
            this.connections.AddRange(connections);
        }
    }

    public void apply_repulsion(Node other)
    {
        Vector3 direction = other.sphere.transform.position - sphere.transform.position;
        float distance = direction.magnitude;
        float force = k * q * q / (distance * distance);
        Vector3 repulsion = direction.normalized * force;
        sphere.transform.position -= repulsion;
    }

    public void apply_attraction(Node other)
    {
        Vector3 direction = other.sphere.transform.position - sphere.transform.position;
        float distance = direction.magnitude;
        float force = spring_constant * distance;
        Vector3 attraction = direction.normalized * force;
        sphere.transform.position += attraction;
    }

    void OnGrab(XRBaseInteractor interactor)
    {
        grabbed = true;
        // 抓取逻辑交给XR系统处理,无需手动同步位置
    }

    void OnRelease(XRBaseInteractor interactor)
    {
        grabbed = false;
    }

    public void Update()
    {
        if (grabbed) return; // 抓取时跳过自主移动逻辑

        // 限制Y轴最低位置
        if (sphere.transform.position.y < 1)
        {
            sphere.transform.position = new Vector3(sphere.transform.position.x, 1, sphere.transform.position.z);
        }

        // 更新所有连接线条的位置
        foreach (Connection connection in connections)
        {
            connection.UpdateLinePositions();
        }
    }
}

public class spawn_graph : MonoBehaviour
{
    Node[] nodes;
    void Start()
    {
        nodes = new Node[10];
        for (int i = 0; i < 10; i++)
        {
            nodes[i] = new Node(new Vector3(Random.Range(-5, 5), Random.Range(0, 10), Random.Range(-5, 5)));
        }

        // 创建连接,避免重复创建相同连接
        HashSet<string> createdConnections = new HashSet<string>();
        for (int i = 0; i < 10; i++)
        {
            int connectionCount = 0;
            while (connectionCount < 3)
            {
                int bIndex = Random.Range(0, 10);
                if (bIndex == i) continue;
                // 用唯一标识避免重复创建连接
                string connectionKey = $"{Mathf.Min(i, bIndex)}-{Mathf.Max(i, bIndex)}";
                if (createdConnections.Contains(connectionKey)) continue;

                createdConnections.Add(connectionKey);
                Connection newConnection = new Connection(nodes[i], nodes[bIndex]);
                nodes[i].connections.Add(newConnection);
                nodes[bIndex].connections.Add(newConnection);
                connectionCount++;
            }
        }
    }

    void Update()
    {
        // 应用排斥力
        for (int i = 0; i < nodes.Length; i++)
        {
            for (int j = 0; j < nodes.Length; j++)
            {
                if (i != j && !nodes[i].grabbed && !nodes[j].grabbed)
                {
                    nodes[i].apply_repulsion(nodes[j]);
                }
            }
        }

        // 更新节点状态
        for (int i = 0; i < nodes.Length; i++)
        {
            nodes[i].Update();
        }
    }
}

关键修改说明

  • 移除自定义position变量:所有位置操作直接使用sphere.transform.position,让XR系统和自主移动逻辑共用同一数据源,彻底消除位置控制冲突。
  • 抓取时跳过自主更新:在Node.Update()中判断grabbed状态,抓取时不执行自主移动和位置限制,完全交给XR控制器控制。
  • 优化连接创建逻辑:通过HashSet生成唯一连接标识,避免重复创建相同连接,同时确保每个连接被两个节点共享,避免重复更新线条。

内容的提问来源于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 03:04:59