自定义接口
本指南展示了如何使用 FastInterface 框架在 realvirtual 中构建您自己的工业通信接口。FastInterface 提供线程安全的高性能通信,具有自动信号管理和连接处理功能。
概述
realvirtual 的 FastInterface 框架使您可以轻松地将仿真连接到外部系统,如 PLC、机器人、IoT 设备或任何具有网络 API 的系统。您只需实现三个核心方法,FastInterface 会自动处理线程、重连、信号管理和 UI 集成。
您将学到:
- 设置 FastInterface 类结构
- 实现连接和通信逻辑
- 处理具有线程安全性的信号交换
- 使用元数据进行协议映射
- 调试和排除接口故障
FastInterface 架构

此图展示了简化的 FastInterface 开发方法:
FastInterface 基础
FastInterfaceBase 类自动提供六项核心服务:
- 线程管理:处理后台线程生命周期
- 自动重连:故障时自动恢复连接
- 信号发现:查找和管理所有子信号组件
- 错误处理:全面的错误恢复和日志记录
- 状态管理:连接状态跟踪和监控
- Unity 集成:无缝的 Inspector UI 和 Unity 兼容性
简单的 3 方法实现
您只需实现 3 个方法即可创建完整的接口:
- EstablishConnection() - 设置您的协议连接(TCP、串口等)
- CommunicationLoop() - 与外部系统持续交换数据
- CloseConnection() - 清理资源并关闭连接
数据流架构
底部展示了线程安全的数据流:
- Unity 线程:管理 GameObject 和信号组件
- 后台线程:安全运行您的协议实现
- 外部系统:您的目标设备(PLC、机器人、IoT、服务器)
信号交换使用内置方法:
- 读取信号:
GetInputsForPLC()- Unity → 外部系统 - 写入信号:
SetOutputsFromPLC(data)- 外部系统 → Unity
核心组件:
- FastInterfaceBase 类 - 提供线程管理、重连逻辑和信号管理基础设施的基础
- 后台线程 - 所有协议通信在专用后台线程上运行,保持 Unity 主线程响应
- 连接状态管理 - 自动连接监控,具有视觉状态指示器和错误报告
- Signal Manager - 处理子信号对象的自动发现,管理线程安全的信号数据交换
- 线程安全通信 - 内置机制,在 Unity 主线程和后台通信线程之间安全传递数据
- 精确时序控制 - 自动集成 realvirtual 的 PrePost FixedUpdate 系统,实现确定性信号处理(在 realvirtual 6.0.5+ 中可用)
标准工作流程:
该架构确保所有 FastInterface 实现的一致模式:
- 初始化 → 将 Inspector 属性复制到线程安全变量
- 连接 → 在后台线程上建立连接,具有自动重试逻辑
- 通信 → 持续循环读取 Unity 信号、发送到外部系统、接收响应并更新 Unity 信号
- 监控 → 具有性能指标的实时连接状态跟踪
- 清理 → 正确的资源清理和优雅关闭
主要优势:
- 一致性 - 所有自定义接口遵循相同的成熟架构模式
- 可靠性 - 内置连接恢复和错误处理
- 性能 - 非阻塞通信,具有优化的信号变更检测
- 可见性 - Inspector 集成显示连接状态、周期计数和时序指标
- 安全性 - 线程安全设计防止 Unity 线程问题
这种标准化方法意味着您可以专注于实现特定的协议逻辑,而 FastInterface 会自动处理所有复杂的线程、连接管理和 Unity 集成。
精确时序控制 (realvirtual 6.0.5+)
FastInterface 自动与 realvirtual 的 PrePost FixedUpdate 系统集成,为工业自动化应用提供确定性时序。此系统确保所有 FastInterface 组件在相对于 Unity 物理计算的精确相同时刻处理信号。
时序架构
PreFixedUpdate → Unity FixedUpdate → PostFixedUpdate
↓ ↓ ↓
PLC→Unity Physics/Logic Unity→PLC
信号处理流程:
- PreFixedUpdate 阶段:所有 FastInterface 实例同步处理 PLC 输出(从 PLC 到 Unity 对象的数据)
- FixedUpdate 阶段:Unity 运行物理计算、组件逻辑和运动更新
- PostFixedUpdate 阶段:所有 FastInterface 实例同步处理 PLC 输入(从 Unity 对象到 PLC 的数据)
无需配置:FastInterface 自动参与时序系统——无需额外的设置或配置。
FastInterface Inspector

FastInterface Inspector 为您的自定义接口提供全面的监控和配置。理解这些字段对于有效的接口开发和故障排除至关重要:
State 部分
- State:显示当前连接状态及视觉指示器:
- 🟢 Connected(绿色)- 接口已连接并成功通信
- 🔴 Disconnected(红色)- 接口未连接
- 🟡 Connecting(黄色)- 接口正在尝试建立连接
- 🟠 Error(橙色)- 接口遇到错误,将尝试重连
- Input Signals:自动发现的输入信号数量(Unity → 外部系统)
- Output Signals:自动发现的输出信号数量(外部系统 → Unity)
- Comm Cycle Ms:实际通信周期时间(毫秒)(性能指标)
- Cycle Count:自连接以来完成的通信周期总数
Configuration 部分
- Update Cycle Ms:通信循环的目标周期时间(毫秒)
- Only Transmit Changed Inputs:✅ 性能优化 - 仅发送更改的输入值
- Auto Reconnect:✅ 连接丢失时自动尝试重连
- Reconnect Interval Seconds:重连尝试之间的等待时间
- Max Reconnect Attempts:最大连续重连尝试次数(-1 = 无限)
- Debug Mode:✅ 启用详细日志记录用于故障排除
Inspector 的主要优势:
- 实时监控 - 在运行时实时查看连接状态和性能指标
- 性能优化 - "Only Transmit Changed Inputs"显著减少网络流量
- 自动恢复 - 内置重连逻辑处理临时连接丢失
- 开发支持 - 调试模式提供详细的故障排除日志
- 信号发现 - 自动计数和管理所有子信号对象
为什么使用 FastInterface:
- 线程安全的后台通信,不阻塞 Unity
- 自动重连,具有可自定义的重试逻辑
- 零配置信号发现 - 自动查找所有子信号
- 高性能,具有优化的信号交换和变更检测
- 内置调试,具有全面的日志记录和状态可视化
新标准:FastInterface 是所有 realvirtual 接口开发的新标准。所有新接口都应使用 FastInterface 以获得最佳性能、可靠性和可维护性。
参考实现:有关生产质量的示例,请查看 realvirtual 代码库中的这些 FastInterface 实现:
* KEBA Interface - 工业机器人控制器通信
* KUKA Interface - KUKA 机器人集成
* MQTT Interface - IoT 消息协议
遗留说明:realvirtual 中的一些较旧接口仍使用已弃用的 InterfaceBaseClass/InterfaceThreadedBaseClass 方法,并将在未来版本中迁移到 FastInterface。
从遗留接口迁移? 如果您有使用已弃用的 InterfaceBaseClass 或 InterfaceThreadedBaseClass 构建的现有接口,请参见遗留接口(已弃用)获取迁移指南和参考文档。
使用模板快速入门
创建自定义接口最快的方法是使用蓝图模板。从 realvirtual 代码库中复制这些工作模板之一:
BlueprintFastInterfaceSimple.cs- 基本协议的最小模板BlueprintFastInterface.cs- 具有高级功能的完整模板
复制 /Assets/realvirtual/Interfaces/Common/Fast/BlueprintFastInterfaceSimple.cs 并将 TODO 部分替换为您的协议逻辑:
using System.Threading;
using System.Threading.Tasks;
namespace realvirtual
{
public class MyCustomInterface : FastInterfaceBase
{
[Header("Connection Settings")]
public string IPAddress = "192.168.1.100";
public int Port = 502;
private TcpClient tcpClient;
private NetworkStream stream;
protected override async Task EstablishConnection(CancellationToken cancellationToken)
{
// 连接到您的系统
tcpClient = new TcpClient();
await tcpClient.ConnectAsync(IPAddress, Port);
stream = tcpClient.GetStream();
}
protected override async Task CommunicationLoop(CancellationToken cancellationToken)
{
// 读取 Unity 信号以发送到外部系统
var inputs = GetInputsForPLC();
// 使用您的协议发送数据
await SendData(inputs);
// 从外部系统接收数据
var receivedData = await ReceiveData();
// 用接收的数据更新 Unity 信号
SetOutputsFromPLC(receivedData);
}
protected override void CloseConnection()
{
stream?.Close();
tcpClient?.Close();
}
}
}
分步实现
步骤 1:创建您的接口类
首先创建一个继承自 FastInterfaceBase 的新 C# 脚本:
using UnityEngine;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Net.Sockets;
namespace realvirtual
{
public class CustomNetworkInterface : FastInterfaceBase
{
[Header("Network Connection")]
public string ServerIP = "192.168.1.100";
public int Port = 8080;
[Header("Protocol Settings")]
public string RequestCommand = "GET_DATA";
public int TimeoutMs = 1000;
// 连接对象
private TcpClient tcpClient;
private NetworkStream stream;
// 用于后台线程访问的线程安全副本
private string threadSafeServerIP;
private int threadSafePort;
}
}
要点:
- 始终继承自
FastInterfaceBase - 为连接设置添加 Inspector 属性
- 将连接对象声明为私有字段
- 使用
[Header]属性组织 Inspector 设置
步骤 2:实现连接逻辑
EstablishConnection 方法在启动通信时在后台线程上运行一次:
protected override async Task EstablishConnection(CancellationToken cancellationToken)
{
ThreadSafeLogger.LogInfo($"Connecting to server {threadSafeServerIP}:{threadSafePort}", GetType().Name);
try
{
tcpClient = new TcpClient();
await tcpClient.ConnectAsync(threadSafeServerIP, threadSafePort);
stream = tcpClient.GetStream();
// 设置套接字选项以实现可靠通信
tcpClient.ReceiveTimeout = 2000;
tcpClient.SendTimeout = 2000;
ThreadSafeLogger.LogInfo("Network connection established", GetType().Name);
}
catch (Exception ex)
{
ThreadSafeLogger.LogError($"Failed to connect: {ex.Message}", GetType().Name);
throw; // 重新抛出以触发重连逻辑
}
}
protected override void CopyPropertiesToThreadSafe()
{
threadSafeServerIP = ServerIP;
threadSafePort = Port;
}
重要:
- 在后台线程中使用
ThreadSafeLogger而非Debug.Log - 连接失败时抛出异常 - FastInterface 会自动处理重连
- 使用
CopyPropertiesToThreadSafe()将 Inspector 属性复制到线程安全字段 - 在连接尝试期间使用
CancellationToken进行响应式关闭
步骤 3:构建通信循环
CommunicationLoop 方法在连接建立后在后台线程上重复运行:
protected override async Task CommunicationLoop(CancellationToken cancellationToken)
{
try
{
// 步骤 1:从 Unity 读取输入信号(要发送到外部系统的数据)
var inputs = GetInputsForPLC();
// 步骤 2:如果我们有输入,则将数据发送到外部系统
if (inputs.Count > 0)
{
await SendDataToServer(inputs);
}
// 步骤 3:从外部系统请求数据
var receivedData = await RequestDataFromServer();
// 步骤 4:用接收的数据更新 Unity 输出信号
if (receivedData.Count > 0)
{
SetOutputsFromPLC(receivedData);
}
}
catch (Exception ex)
{
ThreadSafeLogger.LogError($"Communication error: {ex.Message}", GetType().Name);
throw; // 重新抛出以触发重连
}
}
private async Task SendDataToServer(Dictionary<string, object> inputs)
{
// 将 Unity 信号转换为您的协议格式
var pairs = new List<string>();
foreach (var kv in inputs)
{
pairs.Add($"{kv.Key}={kv.Value}");
}
var message = $"SET:{string.Join(",", pairs)}\r\n";
var messageBytes = Encoding.UTF8.GetBytes(message);
await stream.WriteAsync(messageBytes, 0, messageBytes.Length);
}
private async Task<Dictionary<string, object>> RequestDataFromServer()
{
// 发送请求
var request = Encoding.UTF8.GetBytes("GET:ALL\r\n");
await stream.WriteAsync(request, 0, request.Length);
// 读取响应
var buffer = new byte[1024];
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
var response = Encoding.UTF8.GetString(buffer, 0, bytesRead);
// 将响应解析为 Unity 信号格式
return ParseServerResponse(response);
}
关键概念:
- GetInputsForPLC():以
Dictionary返回所有输入信号值 - SetOutputsFromPLC():用接收的数据更新输出信号
- 信号元数据:使用
TryGetSignalMetadataSafe从后台线程安全访问信号配置() - 错误处理:使用 try/catch 和 ThreadSafeLogger 进行稳健的错误报告
步骤 4:处理信号交换
FastInterface 提供两个用于信号数据交换的主要方法:
// 在 CommunicationLoop 中 - 读取要发送到外部系统的信号
var allInputs = GetInputsForPLC(); // 获取所有输入信号
var changedOnly = GetChangedInputsForPLC(); // 性能优化 - 仅更改的信号
// 用从外部系统接收的数据更新输出信号
var outputData = new Dictionary<string, object>
{
["Motor1_Running"] = true,
["Motor1_Speed"] = 1500,
["Temperature"] = 23.5f,
["Status"] = "Running"
};
SetOutputsFromPLC(outputData);
性能优化:
- 在 Inspector 中使用
OnlyTransmitChangedInputs = true以仅发送更改的输入信号 - 使用
threadSafeOnlyTransmitChangedInputs线程安全地访问此设置 - 使用适当的数据类型 - FastInterface 自动处理转换
步骤 5:实现连接清理
CloseConnection 方法在停止通信时在后台线程上运行:
protected override void CloseConnection()
{
try
{
ThreadSafeLogger.LogInfo("Closing network connection", GetType().Name);
stream?.Close();
stream?.Dispose();
tcpClient?.Close();
tcpClient?.Dispose();
stream = null;
tcpClient = null;
}
catch (Exception ex)
{
ThreadSafeLogger.LogError($"Error during cleanup: {ex.Message}", GetType().Name);
}
}
高级功能
使用信号元数据进行协议映射
信号元数据允许您在每个信号中存储特定于协议的配置:
// 在导入信号时设置元数据(仅主线程)
protected virtual void OnInterfaceStarting()
{
// 查找所有信号并添加 Modbus 元数据
foreach (var signal in this.GetAllSignals())
{
if (signal.name.Contains("Motor"))
{
signal.SetMetadata("RegisterAddress", 100);
signal.SetMetadata("DataType", "INT16");
}
else if (signal.name.Contains("Temperature"))
{
signal.SetMetadata("RegisterAddress", 200);
signal.SetMetadata("DataType", "FLOAT32");
}
}
}
// 从后台线程安全访问元数据
protected override async Task CommunicationLoop(CancellationToken cancellationToken)
{
foreach (var input in GetInputsForPLC())
{
// 线程安全的元数据访问
if (TryGetSignalMetadataSafe<int>(input.Key, "RegisterAddress", out int address))
{
var dataType = GetSignalMetadataSafe<string>(input.Key, "DataType", "INT16");
await WriteRegister(address, ConvertValue(input.Value, dataType));
}
}
}
动态信号导入
您可以使用信号管理 API 动态创建和更新信号:
[Button("Import Modbus Signals")]
private void ImportSignalsFromModbus()
{
// 示例:从 Modbus 配置导入
var signalConfigs = new[]
{
new { Name = "Motor1_Running", Type = SignalType.Bool, Direction = SignalDirection.Output, Address = 100 },
new { Name = "Motor1_Speed", Type = SignalType.Int, Direction = SignalDirection.Input, Address = 101 },
new { Name = "Temperature", Type = SignalType.Float, Direction = SignalDirection.Output, Address = 200 }
};
foreach (var config in signalConfigs)
{
var metadata = new Dictionary<string, object>
{
["RegisterAddress"] = config.Address,
["DataType"] = config.Type.ToString()
};
// 创建或更新带元数据的信号
var signal = this.CreateOrUpdateSignal(config.Name, config.Type, config.Direction, metadata);
Debug.Log($"Created/Updated signal: {signal.name}");
}
// 刷新信号管理器以更新内部缓存
this.RefreshSignalManager();
}
线程安全的属性访问
对于需要后台线程访问的 Inspector 属性,将其复制到线程安全字段:
public class MyInterface : FastInterfaceBase
{
[Header("Settings")]
public string ServerAddress = "192.168.1.100";
public int Timeout = 1000;
public bool EnableExtendedLogging = false;
// 线程安全副本
private string threadSafeServerAddress;
private int threadSafeTimeout;
private bool threadSafeLogging;
// 在后台线程启动前自动调用
protected override void CopyPropertiesToThreadSafe()
{
threadSafeServerAddress = ServerAddress;
threadSafeTimeout = Timeout;
threadSafeLogging = EnableExtendedLogging;
}
protected override async Task EstablishConnection(CancellationToken cancellationToken)
{
// 在后台线程中使用线程安全副本
client.ConnectTimeout = threadSafeTimeout;
await client.ConnectAsync(threadSafeServerAddress, 502);
if (threadSafeLogging)
{
ThreadSafeLogger.LogInfo("Extended logging enabled", GetType().Name);
}
}
}
真实接口示例:有关完整的生产实现,请查看 Assets/realvirtual/Interfaces/ 中基于 FastInterface 的接口:
FastInterface 标准(推荐):
* KEBA Interface - 具有全面错误处理的现代工业机器人控制器
* KUKA Interface - 具有实时通信的高级机器人集成
* MQTT Interface - 具有自动主题管理的 IoT 协议实现
遗留实现(正在迁移):
* S7、OPC-UA 和 Modbus 接口仍使用较旧的方法,但展示了批量操作和协议特定优化等高级功能。
最佳实践
性能优化
- 对输入信号使用变更检测:
// 在 Inspector 或代码中启用
OnlyTransmitChangedInputs = true;
// 或手动检查变更
var changedInputs = GetChangedInputsForPLC();
- 缓存信号元数据以获得更好的性能:
private Dictionary<string, int> signalToRegisterMap = new Dictionary<string, int>();
protected virtual void OnInterfaceStarting()
{
foreach (var signal in this.GetAllSignals())
{
if (signal.GetMetadata<int>("RegisterAddress", out int address))
{
signalToRegisterMap[signal.GetSignalName()] = address;
}
}
}
- 尽可能批量操作:
// 而不是逐个写入寄存器
foreach (var input in inputs)
await WriteSingleRegister(address, value);
// 一次写入多个寄存器
await WriteMultipleRegisters(startAddress, allValues);
线程安全指南
- 切勿从后台线程方法(
EstablishConnection、CommunicationLoop、CloseConnection)访问 Unity GameObject - 在后台线程中使用 ThreadSafeLogger 而非 Debug.Log:
// 后台线程 - 正确
ThreadSafeLogger.LogInfo("Connection established", GetType().Name);
ThreadSafeLogger.LogError("Failed to connect", GetType().Name);
// 后台线程 - 错误
Debug.Log("This will cause errors!");
- 将 Inspector 属性复制到线程安全字段:
// Inspector 属性(仅主线程)
public string ServerIP = "192.168.1.100";
// 线程安全副本(后台线程安全)
private string threadSafeServerIP;
protected override void CopyPropertiesToThreadSafe()
{
threadSafeServerIP = ServerIP; // 自动调用
}
- 使用线程安全的信号元数据访问:
// 后台线程安全
if (TryGetSignalMetadataSafe<int>(signalName, "Address", out int address))
{
// 安全使用 address
}
// 仅主线程
signal.SetMetadata("Address", 100);
错误处理和调试
- 全面的错误处理:
protected override async Task CommunicationLoop(CancellationToken cancellationToken)
{
try
{
await DoProtocolCommunication();
}
catch (SocketException ex)
{
ThreadSafeLogger.LogError($"Network error: {ex.Message}", GetType().Name);
throw; // 重新抛出以触发重连
}
catch (TimeoutException ex)
{
ThreadSafeLogger.LogWarning($"Communication timeout: {ex.Message}", GetType().Name);
throw;
}
catch (Exception ex)
{
ThreadSafeLogger.LogError($"Unexpected error: {ex.Message}", GetType().Name);
throw;
}
}
- 启用调试模式进行详细日志记录:
// 在 Inspector 或代码中设置
DebugMode = true;
// 添加条件日志
if (threadSafeDebugMode)
{
ThreadSafeLogger.LogInfo($"Sending {inputs.Count} signals", GetType().Name);
}
- 在 Inspector 中监控连接状态:
- State:显示当前连接状态及视觉指示器
- CycleCount:完成的通信周期数
- CommCycleMs:实际通信时序
- ErrorMessage:连接失败时的最后一条错误消息
测试您的接口
- 从蓝图开始 - 复制
BlueprintFastInterface.cs作为工作基础 - 先测试连接逻辑:
protected override async Task EstablishConnection(CancellationToken cancellationToken)
{
ThreadSafeLogger.LogInfo("Testing connection...", GetType().Name);
await Task.Delay(100); // 模拟成功连接
ThreadSafeLogger.LogInfo("Connection test successful", GetType().Name);
}
- 添加仿真模式用于无外部硬件的测试:
[Header("Testing")]
public bool SimulationMode = true;
protected override async Task CommunicationLoop(CancellationToken cancellationToken)
{
if (threadSafeSimulationMode)
{
// 模拟接收数据用于测试
var testOutputs = new Dictionary<string, object>
{
["TestBool"] = UnityEngine.Random.value > 0.5f,
["TestInt"] = UnityEngine.Random.Range(0, 100)
};
SetOutputsFromPLC(testOutputs);
}
else
{
// 真实协议实现
await DoRealCommunication();
}
}
故障排除
常见问题
接口无法连接:
- 检查网络连接和防火墙设置
- 验证 IP 地址和端口配置
- 查看 Unity 控制台和接口 Inspector 中的错误消息
- 使用外部工具测试(telnet、协议特定客户端)
信号不更新:
- 验证信号是接口 GameObject 的子对象
- 检查信号方向(Input 与 Output)
- 启用 Debug Mode 进行详细的信号日志记录
- 确保元数据映射正确
性能问题:
- 启用
OnlyTransmitChangedInputs以减少网络流量 - 如果通信过于频繁,增加
UpdateCycleMs - 使用批量操作而非单独写入信号
- 检查 CommunicationLoop 中是否有阻塞操作
线程错误:
- 切勿从后台线程方法访问 Unity GameObject
- 在后台线程中使用 ThreadSafeLogger 而非 Debug.Log
- 将 Inspector 属性复制到线程安全字段
- 使用线程安全的元数据访问方法
调试工具
- Inspector 调试:
- 启用 Debug Mode 进行详细日志记录
- 监控 State 字段查看连接状态
- 检查 ErrorMessage 获取失败详情
- 观察 CycleCount 和 CommCycleMs 了解性能
- 控制台日志:
ThreadSafeLogger.LogInfo($"Processing {inputs.Count} inputs", GetType().Name);
ThreadSafeLogger.LogInfoIf(threadSafeDebugMode, "Debug details here", GetType().Name);
- 信号状态监控:
// 定期记录所有信号值
if (privateCycleCount % 100 == 0)
{
foreach (var input in GetInputsForPLC())
{
ThreadSafeLogger.LogInfo($"Input {input.Key} = {input.Value}", GetType().Name);
}
}
FastInterface API 参考
FastInterface 为自定义实现中的信号操作提供了几种高性能辅助方法。这些方法通过扩展方法提供,并提供线程安全的优化信号访问。
信号值操作
// 类型安全地获取信号值
T GetSignalValue<T>(string signalName)
bool TryGetSignalValue<T>(string signalName, out T value)
// 类型安全地设置信号值
void SetSignalValue<T>(string signalName, T value)
bool TrySetSignalValue<T>(string signalName, T value)
// 在 CommunicationLoop 中的示例:
var motorSpeed = this.GetSignalValue<int>("Motor1_Speed");
var isRunning = this.GetSignalValue<bool>("Motor1_Running");
this.SetSignalValue("Temperature", 23.5f);
this.SetSignalValue("Status", "Connected");
批量信号操作
// 用于通信循环的高性能批量操作
Dictionary<string, object> GetInputsForPLC() // Unity → 外部系统
void SetOutputsFromPLC(Dictionary<string, object> values) // 外部系统 → Unity
// 一次获取所有信号值
Dictionary<string, object> GetAllSignalValues()
// 高效设置多个信号
void SetMultipleSignalValues(Dictionary<string, object> values)
// 使用示例:
protected override async Task CommunicationLoop(CancellationToken cancellationToken)
{
// 读取 Unity 信号以发送到外部系统
var inputs = GetInputsForPLC();
await SendToExternalSystem(inputs);
// 接收数据并更新 Unity 信号
var receivedData = await ReceiveFromExternalSystem();
SetOutputsFromPLC(receivedData);
}
信号发现和管理
// 信号发现和检查
bool HasSignal(string signalName)
int GetSignalCount()
Signal GetSignalComponent(string signalName)
// 按方向获取信号
IEnumerable<Signal> GetInputSignals()
IEnumerable<Signal> GetOutputSignals()
IEnumerable<Signal> GetAllSignals()
// 获取信号名称(线程安全)
List<string> GetInputSignalNames()
List<string> GetOutputSignalNames()
List<string> GetAllSignalNames()
// 使用示例:
protected virtual void OnInterfaceStarting()
{
var inputCount = this.GetInputSignals().Count();
var outputCount = this.GetOutputSignals().Count();
ThreadSafeLogger.LogInfo($"Found {inputCount} inputs, {outputCount} outputs", GetType().Name);
}
线程安全的元数据访问
// 从后台线程安全获取元数据
T GetSignalMetadataSafe<T>(string signalName, string key, T defaultValue = default(T))
bool HasSignalMetadataSafe(string signalName, string key)
Dictionary<string, object> GetAllSignalMetadataSafe(string signalName)
// 设置元数据(仅主线程)
void SetSignalMetadata(string signalName, string key, object value)
// 示例:使用元数据进行寄存器映射
protected override async Task CommunicationLoop(CancellationToken cancellationToken)
{
var inputs = GetInputsForPLC();
foreach (var input in inputs)
{
// 从元数据获取寄存器地址
if (TryGetSignalMetadataSafe<int>(input.Key, "RegisterAddress", out int address))
{
await WriteModbusRegister(address, input.Value);
}
}
}
动态信号创建
// 动态创建信号
Signal CreateSignalSafe(string name, SignalType signalType, SignalDirection direction)
Signal CreateSignalWithMetadata(string name, SignalType signalType, SignalDirection direction,
Dictionary<string, object> metadata = null)
// 创建或更新现有信号
Signal CreateOrUpdateSignal(string signalName, SignalType signalType, SignalDirection direction,
Dictionary<string, object> metadata = null)
// 示例:从外部系统配置导入信号
[Button("Import PLC Signals")]
private void ImportPLCSignals()
{
var signalConfigs = GetPLCConfiguration(); // 您获取 PLC 信号列表的方法
foreach (var config in signalConfigs)
{
var metadata = new Dictionary<string, object>
{
["RegisterAddress"] = config.Address,
["DataType"] = config.Type
};
var signal = this.CreateSignalWithMetadata(
config.Name,
config.SignalType,
config.Direction,
metadata);
ThreadSafeLogger.LogInfo($"Created signal: {signal.name}", GetType().Name);
}
// 创建信号后刷新管理器
this.RefreshSignalManager();
}
Signal Manager 工具
// 创建/修改信号后刷新信号管理器
void RefreshSignalManager()
// 检查高性能模式是否激活
bool IsHighPerformanceModeActive()
// 信号创建后的使用示例:
private void SetupCustomSignals()
{
// 创建您的信号...
CreateSignalSafe("CustomSignal1", SignalType.Bool, SignalDirection.Input);
CreateSignalSafe("CustomSignal2", SignalType.Float, SignalDirection.Output);
// 刷新管理器以启用高性能访问
this.RefreshSignalManager();
}
常见使用模式
模式 1:基于元数据的协议映射
// 设置阶段(主线程)
protected virtual void OnInterfaceStarting()
{
foreach (var signal in this.GetAllSignals())
{
if (signal.name.Contains("Motor"))
{
signal.SetMetadata("RegisterAddress", GetMotorRegisterAddress(signal.name));
signal.SetMetadata("DataType", "INT16");
}
}
this.RefreshSignalManager();
}
// 通信阶段(后台线程)
protected override async Task CommunicationLoop(CancellationToken cancellationToken)
{
var inputs = GetInputsForPLC();
foreach (var input in inputs)
{
if (TryGetSignalMetadataSafe<int>(input.Key, "RegisterAddress", out int address))
{
var dataType = GetSignalMetadataSafe<string>(input.Key, "DataType", "INT16");
await WriteRegisterWithType(address, input.Value, dataType);
}
}
}
模式 2:动态信号导入
[Button("Import from Configuration File")]
private void ImportSignalsFromConfig()
{
var config = LoadConfigurationFile(); // 您的配置加载方法
foreach (var signalDef in config.Signals)
{
var metadata = new Dictionary<string, object>
{
["Address"] = signalDef.Address,
["ScaleFactor"] = signalDef.Scale,
["Unit"] = signalDef.Unit
};
this.CreateOrUpdateSignal(
signalDef.Name,
signalDef.Type,
signalDef.Direction,
metadata);
}
this.RefreshSignalManager();
ThreadSafeLogger.LogInfo($"Imported {config.Signals.Count} signals", GetType().Name);
}
模式 3:高性能通信
protected override async Task CommunicationLoop(CancellationToken cancellationToken)
{
try
{
// 高效批量读取所有 Unity 输入信号
var inputs = GetInputsForPLC();
if (inputs.Count > 0)
{
// 在一次操作中发送所有输入
await SendBatchData(inputs);
}
// 在一次操作中接收所有数据
var outputs = await ReceiveBatchData();
if (outputs.Count > 0)
{
// 在一次批量中更新所有 Unity 输出信号
SetOutputsFromPLC(outputs);
}
}
catch (Exception ex)
{
ThreadSafeLogger.LogError($"Communication error: {ex.Message}", GetType().Name);
throw; // 重新抛出以触发重连
}
}
性能提示:
* 使用批量操作(GetInputsForPLC()、SetOutputsFromPLC())而非单独访问信号以获得更好的性能
* 创建或修改信号后调用 RefreshSignalManager() 以启用高性能查找
* 使用元数据进行协议特定配置,避免硬编码地址或参数
* 始终在后台线程方法中使用 ThreadSafeLogger 而非 Debug.Log