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; } }
问题原因分析
- Update函数冗余赋值干扰:在
!listener.Pending()分支中重复执行旋转赋值,会打断协程的平滑插值过程;若TCP断连后endRotation未更新,会强制将旋转重置为旧值。 - 协程重复启动冲突:每次TCP数据到来都会启动新的旋转协程,多个协程同时修改关节旋转,旧协程结束时会覆盖新的旋转值,表现为“自动复位”。
- Vuforia标记事件设置问题:若识别标记的
On Tracking Lost事件设置为禁用物体或重置Transform,标记短暂丢失后会带动机械臂复位。 - 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
相关产品推荐
相关产品推荐

