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
相关产品推荐
相关产品推荐

