今天心血来潮登录CHATGPT,看看能不能在Unity方面也能那么智能化,结果被上了一课。
以下是对话流程:
Ps:本篇文章是关于Unity串口接收数据的讨论,本人没有试过好不好用,如果想直接使用代码,请翻到最下面往上看。
正文:
我:编写UNITY可以使用的串口接收数据脚本
ChatGPT 说:
在Unity中接收串口数据的脚本可以通过 System.IO.Ports 命名空间来实现。这可以让你从串口接收数据并将其处理或展示在游戏引擎内。以下是一个简单的示例脚本,用于在Unity中接收串口数据:
-
引入命名空间
首先,你需要确保使用了System.IO.Ports命名空间来处理串口通信。 -
编写代码
using System;
using System.IO.Ports; // 引入串口命名空间
using UnityEngine;
public class SerialPortReader : MonoBehaviour
{
public string portName = "COM3"; // 设置串口名称
public int baudRate = 9600; // 设置波特率
private SerialPort serialPort; // 定义串口对象
void Start()
{
// 初始化串口
serialPort = new SerialPort(portName, baudRate);
serialPort.ReadTimeout = 1000; // 设置读取超时
try
{
serialPort.Open(); // 打开串口
Debug.Log("串口已打开");
}
catch (Exception e)
{
Debug.LogError("无法打开串口: " + e.Message);
}
}
void Update()
{
if (serialPort != null && serialPort.IsOpen)
{
try
{
string data = serialPort.ReadLine(); // 读取一行数据
Debug.Log("接收到的数据: " + data);
// 这里可以处理接收到的数据,例如解析或展示
ProcessData(data);
}
catch (TimeoutException)
{
// 处理超时情况,这里可以选择不做任何事
}
}
}
void OnApplicationQuit()
{
// 确保在程序退出时关闭串口
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Close();
Debug.Log("串口已关闭");
}
}
// 处理数据的函数
private void ProcessData(string data)
{
// 你可以在这里解析数据并应用到游戏逻辑中
// 例如,如果串口数据控制游戏对象移动,可以在这里实现相应的逻辑
}
}
- 脚本说明
portName: 串口端口名称,例如"COM3"(在Windows上)或"/dev/ttyUSB0"(在Linux或macOS上)。
baudRate: 波特率,通常与发送数据设备的波特率相同。
serialPort.ReadLine(): 从串口读取一行数据。此方法会阻塞,直到接收到换行符(\n)或遇到超时。
ProcessData: 用于处理从串口接收到的数据,你可以在这个方法里根据需要解析数据,并执行相关的操作。 - 串口设置
在运行脚本前,请确保:
串口的端口号和波特率与数据发送端一致。
数据发送端按行发送数据,如果没有换行符(如\n),可以用 serialPort.Read() 或 serialPort.ReadExisting() 代替 ReadLine()。
6. 常见问题
无法打开串口:确保你使用的串口名称正确,并且设备连接正常。如果其他程序占用了该串口,Unity将无法打开它。
串口未收到数据:检查设备是否正确发送了数据,并确保波特率设置匹配。
希望这个脚本能帮助你在Unity中实现串口通信!如果你需要进一步修改或扩展功能,随时告诉我。
我说:
改进以上脚本,使用委托的方式
ChatGPT 说:
ChatGPT
要改进上述脚本并使用委托(delegates)处理串口数据,我们可以引入一个委托来响应从串口接收到的数据。这样可以更灵活地将串口数据传递给其他脚本或处理逻辑,而不需要直接在Update中处理。
改进后的脚本:
using System;
using System.IO.Ports; // 引入串口命名空间
using UnityEngine;
public class SerialPortReader : MonoBehaviour
{
public string portName = "COM3"; // 设置串口名称
public int baudRate = 9600; // 设置波特率
private SerialPort serialPort; // 定义串口对象
// 定义一个委托,用于处理接收到的串口数据
public delegate void OnDataReceivedDelegate(string data);
public event OnDataReceivedDelegate OnDataReceived; // 定义事件,供外部订阅
void Start()
{
// 初始化串口
serialPort = new SerialPort(portName, baudRate);
serialPort.ReadTimeout = 1000; // 设置读取超时
try
{
serialPort.Open(); // 打开串口
Debug.Log("串口已打开");
}
catch (Exception e)
{
Debug.LogError("无法打开串口: " + e.Message);
}
}
void Update()
{
if (serialPort != null && serialPort.IsOpen)
{
try
{
string data = serialPort.ReadLine(); // 读取一行数据
Debug.Log("接收到的数据: " + data);
// 触发数据接收事件,通知所有订阅者
OnDataReceived?.Invoke(data);
}
catch (TimeoutException)
{
// 处理超时情况,这里可以选择不做任何事
}
}
}
void OnApplicationQuit()
{
// 确保在程序退出时关闭串口
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Close();
Debug.Log("串口已关闭");
}
}
}
改进点:
使用委托和事件:
我们定义了一个名为 OnDataReceivedDelegate 的委托,并创建了一个事件 OnDataReceived。这个事件允许外部的其他脚本订阅,以便在串口接收到数据时能够响应。
当串口接收到数据时,使用 OnDataReceived?.Invoke(data) 触发事件,将接收到的数据传递给所有的订阅者。
外部订阅事件:
你可以在另一个脚本中订阅 OnDataReceived 事件,以便处理串口数据。
在另一个脚本中订阅事件:
using UnityEngine;
public class DataProcessor : MonoBehaviour
{
public SerialPortReader serialPortReader; // 引用到SerialPortReader脚本
void Start()
{
// 订阅串口数据接收事件
serialPortReader.OnDataReceived += HandleDataReceived;
}
// 当接收到串口数据时,执行此方法
private void HandleDataReceived(string data)
{
Debug.Log("处理串口数据: " + data);
// 在这里可以处理接收到的数据,例如控制游戏对象的行为
}
void OnDestroy()
{
// 取消订阅,防止内存泄漏
if (serialPortReader != null)
{
serialPortReader.OnDataReceived -= HandleDataReceived;
}
}
}
改进的优势:
解耦代码:串口数据的处理与串口通信本身分离。SerialPortReader 脚本只负责接收数据并触发事件,处理逻辑可以在其他地方实现。
灵活性:通过事件机制,多个脚本可以同时订阅 OnDataReceived 事件,从而在多个地方对数据进行处理。
代码复用:事件处理可以很容易复用,多个组件可以监听相同的数据源,并作出不同的反应。
注意事项:
线程安全:如果串口数据接收涉及多线程场景,确保在主线程上调用事件。例如,使用 Unity’s Main Thread Dispatcher 来切换回主线程处理UI等任务。
取消订阅:在对象销毁时要记得取消订阅事件,防止内存泄漏或空引用异常。
通过这种方式,你可以将串口数据接收和处理逻辑分开,增加代码的可维护性和可扩展性。
我说:
由你来改进以上脚本,使用最优化的方式
ChatGPT 说:
ChatGPT
要进一步优化上述脚本,我们可以从几个方面入手:性能、代码简洁性、可维护性和更好地管理资源(如串口的打开与关闭)。此外,我们可以考虑使用一个独立的线程处理串口数据读取,以避免阻塞 Update() 方法,并确保在主线程中处理Unity相关的逻辑。
以下是改进的脚本,重点在于:
使用独立线程来处理串口数据读取,防止阻塞主线程。
使用线程安全的方法将串口数据从后台线程传递到主线程。
通过事件系统解耦代码,保持可扩展性和灵活性。
改进后的脚本:
using System;
using System.IO.Ports;
using System.Threading;
using UnityEngine;
public class SerialPortReader : MonoBehaviour
{
public string portName = "COM3"; // 串口名称
public int baudRate = 9600; // 波特率
private SerialPort serialPort; // 串口对象
private Thread serialThread; // 用于读取串口的线程
private bool isRunning = false; // 控制线程的标志
private string receivedData; // 存储接收到的数据
private bool dataReceived = false; // 标识是否收到新数据
// 定义委托和事件,用于处理接收到的数据
public delegate void OnDataReceivedDelegate(string data);
public event OnDataReceivedDelegate OnDataReceived;
void Start()
{
// 初始化串口
serialPort = new SerialPort(portName, baudRate);
serialPort.ReadTimeout = 1000; // 设置读取超时
try
{
serialPort.Open(); // 打开串口
isRunning = true; // 设置运行标志
serialThread = new Thread(ReadSerialPort); // 启动串口读取线程
serialThread.Start();
Debug.Log("串口已打开并正在监听数据...");
}
catch (Exception e)
{
Debug.LogError("无法打开串口: " + e.Message);
}
}
void Update()
{
// 如果收到数据,将其传递给主线程
if (dataReceived)
{
dataReceived = false; // 重置标志位
OnDataReceived?.Invoke(receivedData); // 触发事件
}
}
// 串口读取线程
private void ReadSerialPort()
{
while (isRunning && serialPort.IsOpen)
{
try
{
// 读取数据
string data = serialPort.ReadLine();
lock (this)
{
receivedData = data; // 储存数据
dataReceived = true; // 标识新数据已到达
}
}
catch (TimeoutException)
{
// 读取超时,不做处理
}
}
}
void OnApplicationQuit()
{
// 确保关闭串口和终止线程
isRunning = false; // 停止线程循环
if (serialThread != null && serialThread.IsAlive)
{
serialThread.Join(); // 等待线程结束
}
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Close(); // 关闭串口
Debug.Log("串口已关闭");
}
}
}
主要优化点:
使用线程读取串口:
将串口读取放到一个独立的线程中执行,防止阻塞主线程。这样,即使串口等待数据的时间较长,也不会影响游戏的渲染和响应。
线程安全的数据传递:
通过 lock 关键字确保串口读取线程和主线程之间的数据访问是线程安全的。
使用标志 dataReceived 使得数据只在主线程的 Update 方法中处理,保证与Unity相关的操作(如触发事件)都在主线程中执行。
防止线程泄漏:
使用 isRunning 控制线程的执行,并在应用退出时使用 Join() 来确保线程安全退出。
委托和事件:
OnDataReceived 委托和事件继续保留,作为数据处理的回调机制,外部可以订阅该事件来处理串口数据,保持灵活性。
订阅事件的外部脚本:
与之前的实现类似,你可以在其他脚本中订阅 OnDataReceived 事件。
using UnityEngine;
public class DataProcessor : MonoBehaviour
{
public SerialPortReader serialPortReader; // 引用到 SerialPortReader 脚本
void Start()
{
// 订阅串口数据接收事件
serialPortReader.OnDataReceived += HandleDataReceived;
}
// 处理接收到的数据
private void HandleDataReceived(string data)
{
Debug.Log("接收到的数据处理: " + data);
// 在这里可以解析数据,并应用到游戏对象或其他逻辑
}
void OnDestroy()
{
// 确保取消订阅事件,防止内存泄漏
if (serialPortReader != null)
{
serialPortReader.OnDataReceived -= HandleDataReceived;
}
}
}
优化总结:
线程处理:将耗时操作从主线程分离,提高了性能,避免了游戏卡顿。
线程安全:确保在多线程环境下的变量安全共享,防止数据竞争。
事件机制:通过事件触发数据处理,解耦代码,增加了灵活性和可扩展性。
资源管理:确保在程序退出时,正确关闭串口并终止后台线程,防止资源泄漏。
这种优化不仅提高了性能,也提高了代码的安全性和可维护性。
我说:
有点看不懂 能不能使用简单的方式给我解释一下使用方法
ChatGPT 说:
ChatGPT
没问题!我来用简单的方式给你解释一下如何使用这个优化后的串口接收数据脚本,并介绍其工作原理。
简单使用步骤:
- 在Unity项目中添加串口读取脚本:
首先,把这个脚本添加到你的项目中,可以新建一个 C# 脚本,命名为 SerialPortReader.cs,然后将以下代码复制进去。
using System;
using System.IO.Ports;
using System.Threading;
using UnityEngine;
public class SerialPortReader : MonoBehaviour
{
public string portName = "COM3"; // 设置串口名称
public int baudRate = 9600; // 设置波特率
private SerialPort serialPort; // 串口对象
private Thread serialThread; // 用于读取串口的线程
private bool isRunning = false; // 控制线程的标志
private string receivedData; // 存储接收到的数据
private bool dataReceived = false; // 标识是否收到新数据
// 定义委托和事件,用于处理接收到的数据
public delegate void OnDataReceivedDelegate(string data);
public event OnDataReceivedDelegate OnDataReceived;
void Start()
{
// 初始化串口
serialPort = new SerialPort(portName, baudRate);
serialPort.ReadTimeout = 1000; // 设置读取超时
try
{
serialPort.Open(); // 打开串口
isRunning = true; // 设置运行标志
serialThread = new Thread(ReadSerialPort); // 启动串口读取线程
serialThread.Start();
Debug.Log("串口已打开并正在监听数据...");
}
catch (Exception e)
{
Debug.LogError("无法打开串口: " + e.Message);
}
}
void Update()
{
// 如果收到数据,将其传递给主线程
if (dataReceived)
{
dataReceived = false; // 重置标志位
OnDataReceived?.Invoke(receivedData); // 触发事件
}
}
// 串口读取线程
private void ReadSerialPort()
{
while (isRunning && serialPort.IsOpen)
{
try
{
// 读取数据
string data = serialPort.ReadLine();
lock (this)
{
receivedData = data; // 储存数据
dataReceived = true; // 标识新数据已到达
}
}
catch (TimeoutException)
{
// 读取超时,不做处理
}
}
}
void OnApplicationQuit()
{
// 确保关闭串口和终止线程
isRunning = false; // 停止线程循环
if (serialThread != null && serialThread.IsAlive)
{
serialThread.Join(); // 等待线程结束
}
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Close(); // 关闭串口
Debug.Log("串口已关闭");
}
}
}
- 添加脚本到游戏对象:
打开Unity,创建一个空的 GameObject。
将 SerialPortReader.cs 脚本拖到该 GameObject 上,这样这个脚本就附加到这个游戏对象上了。 - 设置串口:
点击添加了脚本的 GameObject,在 Inspector 面板里,你会看到串口配置项:
Port Name: 输入你设备的串口号,比如在Windows上是 COM3 或者在Linux上是 /dev/ttyUSB0。
Baud Rate: 设置波特率,与你串口设备的波特率匹配,比如常见的 9600。 - 订阅数据事件(可选):
如果你想处理接收到的数据,你可以通过其他脚本订阅 OnDataReceived 事件。下面是一个简单的例子,说明如何订阅并处理数据。
using UnityEngine;
public class DataProcessor : MonoBehaviour
{
public SerialPortReader serialPortReader; // 引用到 SerialPortReader 脚本
void Start()
{
// 订阅串口数据接收事件
serialPortReader.OnDataReceived += HandleDataReceived;
}
// 处理接收到的数据
private void HandleDataReceived(string data)
{
Debug.Log("接收到的数据处理: " + data);
// 在这里可以解析数据,并应用到游戏对象或其他逻辑
}
void OnDestroy()
{
// 确保取消订阅事件,防止内存泄漏
if (serialPortReader != null)
{
serialPortReader.OnDataReceived -= HandleDataReceived;
}
}
}
- 将 DataProcessor 添加到另一个对象:
再创建一个空的 GameObject,命名为 DataProcessor。
将上面这个 DataProcessor 脚本添加到这个 GameObject 上。
在 Inspector 面板中,把 SerialPortReader 游戏对象拖到 DataProcessor 的 SerialPortReader 引用框中,链接两个脚本。
简单的工作原理:
线程读取串口数据:
脚本中的串口读取是放在一个独立的线程中进行的,不会阻塞Unity的主线程。这个线程会不断监听串口,并把接收到的数据存储到 receivedData 中。
主线程处理数据:
在 Update() 方法中,脚本会检查是否有新数据。如果有,就会触发 OnDataReceived 事件,把数据传递给其他订阅者。
事件订阅:
通过订阅 OnDataReceived 事件,你可以在其他脚本中处理串口传来的数据。无论是显示在游戏中,还是控制某些游戏元素,都可以通过这个事件实现。
关闭串口和线程:
当游戏退出时,OnApplicationQuit() 确保串口和线程都正常关闭,防止资源泄漏。
举个例子:
假设你的串口设备发送的是温度数据。每次接收到数据后,Unity的 DataProcessor 脚本可以将数据处理,比如显示在游戏界面上或控制某个物体移动。
总结:
串口读取 在后台线程中进行,主线程 只负责处理接收到的数据。
使用事件 (OnDataReceived) 使得数据处理更灵活,可以方便地与其他脚本交互。
设置简单,你只需要将串口号和波特率填好,订阅事件即可处理数据。
如果还有什么不明白的地方,随时问我!
PS:CHATGPT确实强,这还用我这种菜鸟写个*啊,回家考公算了,苦笑.jpg