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

Unity+Vuforia环境下AR机械臂旋转后自动复位问题求助

Unity+Vuforia开发:AR机械臂旋转后自动复位问题排查

问题场景

通过TCP/IP接收遥控器发送的机械臂(运动链)各关节角度数据,解析后用Slerp控制关节局部旋转。无Vuforia时代码运行正常,将机械臂挂载到Vuforia识别标记上后,关节可旋转但随后会自动复位至初始姿态。

问题代码

using UnityEngine;
using Vuforia;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using TMPro;

public class MyListener : MonoBehaviour
{
    public int connectionPort = 25001;
    TcpListener listener;
    string[] stringArray;
    float[] Objetivo;  
    float[] posInicial;
    float[] currAngle;
    string msg;
    public Transform[] Link = new Transform[6];
    Quaternion[] startRotation = new Quaternion[6];
    Quaternion[] currentRotation = new Quaternion[6];
    Quaternion[] smoothRotation = new Quaternion[6];
    Quaternion[] endRotation = new Quaternion[6];

    int flagGiro, remoto;   
    public int flag;
    float duration = 3f;

    public TMP_Text Text1;
    public TMP_Text Text2;
    public TMP_Text Text3;
    public TMP_Text Text4;
    public TMP_Text Text5;
    public TMP_Text Text6;

    void Start()
    {
        flagGiro = 0;
        flag=0;
        remoto = 0;
        listener = new TcpListener(IPAddress.Any, connectionPort);
        listener.Start();
        for (int i=0;i<6;i++){
            startRotation[i] = Quaternion.Euler(0, 0, 0);
        }
    }
   
    void Update()
    {
        if (!listener.Pending()){
            flag=0; 
            for (int i = 0; i < 6; i++)
            {
                Link[i].localRotation = endRotation[i]; 
            }
            if (remoto==1)
            {
                for (int i = 0; i < 6; i++)
                {
                    Link[i].localRotation = endRotation[i];
                }
            }
        } 
        else {
            TcpClient client = listener.AcceptTcpClient();
            NetworkStream ns = client.GetStream();
            StreamReader reader = new StreamReader(ns);
            msg = reader.ReadToEnd();
            stringArray = ParseData(msg);            
            Text1.text = stringArray[0];
            Text2.text = stringArray[1];
            Text3.text = stringArray[2];
            Text4.text = stringArray[3];
            Text5.text = stringArray[4];
            Text6.text = stringArray[5];

            float y = float.Parse(stringArray[0]);
            float x = float.Parse(stringArray[1]);
            float x1 = float.Parse(stringArray[2]);
            float y1 = float.Parse(stringArray[3]);
            float z = float.Parse(stringArray[4]);
            float x3 = float.Parse(stringArray[5]);

            endRotation[0] = Quaternion.Euler(Link[0].localEulerAngles.x, y, Link[0].localEulerAngles.z);
            endRotation[1] = Quaternion.Euler(x, Link[1].localEulerAngles.y, Link[1].localEulerAngles.z);
            endRotation[2] = Quaternion.Euler(x1, Link[2].localEulerAngles.y, Link[2].localEulerAngles.z);
            endRotation[3] = Quaternion.Euler(Link[3].localEulerAngles.x, y1, Link[3].localEulerAngles.z);
            endRotation[4] = Quaternion.Euler(Link[4].localEulerAngles.x, Link[4].localEulerAngles.y, z);
            endRotation[5] = Quaternion.Euler(x3, Link[5].localEulerAngles.y, Link[5].localEulerAngles.z); 

            for (int i = 0; i < 6; i++)
            {
                startRotation[i] = Link[i].localRotation;
            } 
            StartCoroutine(RotateObject(duration));
        }
    }

    public static string[] ParseData(string dataString)
    {
        Debug.Log(dataString);
        if (dataString[0]==':'){
            dataString = dataString.Remove(dataString.Length-4);
            dataString = dataString.Substring(1);       
        }
        string[] stringArray = dataString.Split('/');
        return stringArray;
    }
    
    IEnumerator RotateObject(float duration)
    {
        for (int i = 0; i < 6; i++)
        {
            startRotation[i] = Link[i].localRotation;
        }
        
        float elapsedTime = 0f;
        while (elapsedTime < duration)
        {
            for (int i = 0; i < 6; i++)
            {
                smoothRotation[i] = Quaternion.Slerp(startRotation[i], endRotation[i], elapsedTime / duration);
                Link[i].localRotation = smoothRotation[i];
            }
            elapsedTime += Time.deltaTime;
            yield return null;
        }

        for (int i = 0; i < 6; i++)
        {
            Link[i].localRotation = endRotation[i]; 
            Vector3 endPosition = Link[i].localPosition;
            Link[i].localPosition = endPosition;
        }
        remoto = 1;
    }
}

问题原因分析

  1. Update函数冗余赋值干扰:在!listener.Pending()分支中重复执行旋转赋值,会打断协程的平滑插值过程;若TCP断连后endRotation未更新,会强制将旋转重置为旧值。
  2. 协程重复启动冲突:每次TCP数据到来都会启动新的旋转协程,多个协程同时修改关节旋转,旧协程结束时会覆盖新的旋转值,表现为“自动复位”。
  3. Vuforia标记事件设置问题:若识别标记的On Tracking Lost事件设置为禁用物体或重置Transform,标记短暂丢失后会带动机械臂复位。
  4. endRotation初始化缺失:Start()中未初始化endRotation数组,首次运行时使用默认的Quaternion.identity,导致初始旋转异常。

修复方案

1. 优化Update函数逻辑

移除冗余赋值,添加协程冲突处理:

void Update()
{
    if (listener.Pending())
    {
        TcpClient client = listener.AcceptTcpClient();
        NetworkStream ns = client.GetStream();
        StreamReader reader = new StreamReader(ns);
        msg = reader.ReadToEnd();
        stringArray = ParseData(msg);            
        Text1.text = stringArray[0];
        Text2.text = stringArray[1];
        Text3.text = stringArray[2];
        Text4.text = stringArray[3];
        Text5.text = stringArray[4];
        Text6.text = stringArray[5];

        float y = float.Parse(stringArray[0]);
        float x = float.Parse(stringArray[1]);
        float x1 = float.Parse(stringArray[2]);
        float y1 = float.Parse(stringArray[3]);
        float z = float.Parse(stringArray[4]);
        float x3 = float.Parse(stringArray[5]);

        // 固定非目标轴的初始值,避免父物体姿态干扰
        endRotation[0] = Quaternion.Euler(0, y, 0); 
        endRotation[1] = Quaternion.Euler(x, 0, 0);
        endRotation[2] = Quaternion.Euler(x1, 0, 0);
        endRotation[3] = Quaternion.Euler(0, y1, 0);
        endRotation[4] = Quaternion.Euler(0, 0, z);
        endRotation[5] = Quaternion.Euler(x3, 0, 0); 

        // 停止旧协程,避免冲突
        StopAllCoroutines();
        StartCoroutine(RotateObject(duration));
    }
    // 仅在远程控制激活时维持目标姿态
    else if (remoto == 1)
    {
        for (int i = 0; i < 6; i++)
        {
            Link[i].localRotation = endRotation[i];
        }
    }
}

2. 初始化endRotation数组

在Start()中添加初始化代码:

void Start()
{
    flagGiro = 0;
    flag=0;
    remoto = 0;
    listener = new TcpListener(IPAddress.Any, connectionPort);
    listener.Start();
    for (int i=0;i<6;i++){
        startRotation[i] = Quaternion.Euler(0, 0, 0);
        endRotation[i] = Quaternion.Euler(0, 0, 0); // 初始化目标旋转
    }
}

3. 检查Vuforia标记事件设置

选中识别标记的GameObject,在Inspector中修改Default Observer Event Handler:

  • 移除On Tracking Lost事件中SetActive(false)或重置Transform的操作;
  • 若需保持姿态,可将该事件设为无操作。

4. 优化旋转协程

移除冗余的位置赋值:

IEnumerator RotateObject(float duration)
{
    for (int i = 0; i < 6; i++)
    {
        startRotation[i] = Link[i].localRotation;
    }
    
    float elapsedTime = 0f;
    while (elapsedTime < duration)
    {
        for (int i = 0; i < 6; i++)
        {
            smoothRotation[i] = Quaternion.Slerp(startRotation[i], endRotation[i], elapsedTime / duration);
            Link[i].localRotation = smoothRotation[i];
        }
        elapsedTime += Time.deltaTime;
        yield return null;
    }

    for (int i = 0; i < 6; i++)
    {
        Link[i].localRotation = endRotation[i]; 
    }
    remoto = 1;
}

额外建议

  • 保持机械臂的运动链层级:每个关节的父物体为上一个关节,根关节挂载到Vuforia标记下,确保局部旋转的相对性正确;
  • 添加日志输出监控旋转变化:如Debug.Log($"Link[{i}] localRotation: {Link[i].localRotation}"),定位异常触发时机。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 15:19:54