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

Unity开启串口后动画卡顿,如何优化实现流畅动画?

问题描述

我正在开发一个简单的Unity程序,通过C#接收Arduino发送的串口数据,根据接收的值播放两种不同的动画。

Arduino代码

void setup() {
  Serial.begin(9600);
}

void loop() {
  int val=random(0,1);
  if(val==1){
    Serial.flush();
    Serial.println(1);
  }
  else{
    Serial.flush();
    Serial.println(0);
    }
  }
  delay(500);
}

Unity C#代码

using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.IO.Ports;
using UnityEngine;

public class ard : MonoBehaviour
{
    private SerialPort data_stream = new SerialPort("COM3", 9600);
    public GameObject kid;

    // Start is called before the first frame update
    void Start()
    {
        data_stream.Open();
    }

    // Update is called once per frame
    void Update()
    {
        if (data_stream.IsOpen) { 
            string val=data_stream.ReadLine();
            if (val == "0") {
                kid.GetComponent<Animator>().Play("Standing Idle To Fight Idle");
            }
            else if(val == "1")
            {
                kid.GetComponent<Animator>().Play("Fast Run");
            }
        }

    }
}

目前Arduino的数据发送和Unity的数据接收均正常,场景中仅使用Mixamo的男孩模型。问题是即使使用RTX3050显卡,开启串口后动画仍非常卡顿、不流畅;若将串口代码替换为普通键盘输入,动画则运行流畅。需要修改代码以实现接收串口数据的同时流畅播放动画。


解决方案

卡顿的核心原因是SerialPort.ReadLine()是阻塞式调用,会卡住Unity的主线程(Update运行在主线程),导致帧刷新被打断。下面是具体的修改方案:

1. 用子线程处理串口读取,避免阻塞主线程

把串口数据的读取逻辑放到独立子线程中,主线程只负责处理动画切换逻辑,两者通过线程安全的队列传递数据:

修改后的Unity C#代码:

using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.IO.Ports;
using UnityEngine;
using System.Collections.Concurrent;

public class ard : MonoBehaviour
{
    private SerialPort data_stream;
    public GameObject kid;
    private Thread serialThread;
    private ConcurrentQueue<string> serialDataQueue = new ConcurrentQueue<string>();
    private bool isRunning = false;
    private Animator anim;

    void Start()
    {
        // 缓存Animator组件,避免重复调用GetComponent
        anim = kid.GetComponent<Animator>();
        
        data_stream = new SerialPort("COM3", 9600);
        // 设置读取超时,避免子线程无限阻塞
        data_stream.ReadTimeout = 100;
        try
        {
            data_stream.Open();
            isRunning = true;
            // 启动子线程处理串口读取
            serialThread = new Thread(ReadSerialData);
            serialThread.Start();
        }
        catch(System.Exception e)
        {
            Debug.LogError("串口打开失败: " + e.Message);
        }
    }

    void ReadSerialData()
    {
        while(isRunning && data_stream.IsOpen)
        {
            try
            {
                string val = data_stream.ReadLine();
                // 去掉换行符和空格,避免匹配失败
                val = val.Trim();
                if(!string.IsNullOrEmpty(val))
                {
                    serialDataQueue.Enqueue(val);
                }
            }
            catch(System.TimeoutException)
            {
                // 超时属于正常情况,无需报错
                Thread.Sleep(10);
            }
            catch(System.Exception e)
            {
                Debug.LogError("串口读取错误: " + e.Message);
                Thread.Sleep(100); // 出错时短暂休眠,避免死循环占用CPU
            }
        }
    }

    void Update()
    {
        if(serialDataQueue.TryDequeue(out string val))
        {
            if(val == "0")
            {
                // 只有当前动画不是目标动画时才切换,避免频繁触发导致卡顿
                if(!anim.GetCurrentAnimatorStateInfo(0).IsName("Standing Idle To Fight Idle"))
                {
                    anim.Play("Standing Idle To Fight Idle");
                }
            }
            else if(val == "1")
            {
                if(!anim.GetCurrentAnimatorStateInfo(0).IsName("Fast Run"))
                {
                    anim.Play("Fast Run");
                }
            }
        }
    }

    void OnDestroy()
    {
        isRunning = false;
        if(serialThread != null && serialThread.IsAlive)
        {
            serialThread.Join();
        }
        if(data_stream != null && data_stream.IsOpen)
        {
            data_stream.Close();
        }
    }
}

2. 优化Arduino代码,修正逻辑并减少延迟

原Arduino代码中random(0,1)只会生成0(因为random的上限是开区间),永远发0,建议修正为random(0,2)才能生成0和1。另外不需要每次发送都调用Serial.flush(),它会等待所有数据发送完成,反而增加延迟:

修改后的Arduino代码:

void setup() {
  Serial.begin(9600);
}

void loop() {
  int val = random(0, 2); // 生成0或1
  Serial.println(val);
  delay(500);
}

3. 额外优化建议

  • 确保Unity项目的垂直同步已开启(Edit > Project Settings > Quality > VSync Count),避免帧波动
  • 关闭Unity编辑器的Gizmos和Stats面板,减少编辑器额外性能开销
  • 如果串口数据频率较高,可以在子线程中做数据去重,避免队列中堆积大量重复指令

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 13:02:16