问题回顾:在摄像头手势识别项目中,我使用 UDP 协议将 21 个关键点的三维坐标实时发送给 Unity,用于驱动场景中的手部骨架模型。然而在实际运行中,Unity 虽然成功接收到了数据,但所有节点对应的小球始终固定在初始位置,未能按照接收数据更新场景,导致虚拟手无法动起来。
Unity 子线程禁止修改 Transform
❗ 问题描述
在 Unity 中,尝试在子线程中执行以下代码:
handJoints[i].localPosition = pos;
会触发错误:
localPosition can only be set from the main thread.
这是因为 UnityEngine 的大多数 API(如 Transform、Renderer、Camera 等)不是线程安全的,只能在主线程中访问。
✅ 正确做法:将操作调度到主线程中执行
我们应当在子线程中只处理数据接收,再通过主线程去执行位置设置。
📌 使用 MainThreadDispatcher 工具
第一步:新建 MainThreadDispatcher.cs
脚本
using System;
using System.Collections.Generic;
using UnityEngine;
public class MainThreadDispatcher : MonoBehaviour
{
private static readonly Queue<Action> actions = new Queue<Action>();
public static void Enqueue(Action action)
{
lock (actions)
{
actions.Enqueue(action);
}
}
void Update()
{
lock (actions)
{
while (actions.Count > 0)
{
actions.Dequeue()?.Invoke();
}
}
}
}
第二步:添加到场景
将该脚本挂载到场景中任意一个 GameObject 上,建议创建一个空物体,命名为 MainThreadDispatcherHost
并挂载此脚本。
第三步:在 HandReceiver.cs
中替代原始写法
将原本在子线程中执行的代码:
handJoints[i].localPosition = pos;
改写为主线程调度方式:
int index = i;
Vector3 capturedPos = pos;
MainThreadDispatcher.Enqueue(() =>
{
handJoints[index].localPosition = capturedPos;
});
✅ 最终效果
- UDP 接收仍然在后台子线程处理;
- 所有对
Transform
的修改都由 Unity 主线程安全完成; - 报错问题消失,逻辑清晰、稳定运行。