> 技术文档 > Unity开发记录_12_PC / Web工业仿真项目 使用S7.Net 和 博图TIA Portal V16 和 S7-PLCSIM Advanced V3.0 的开源项目(二)_unity 交换机仿真项目

Unity开发记录_12_PC / Web工业仿真项目 使用S7.Net 和 博图TIA Portal V16 和 S7-PLCSIM Advanced V3.0 的开源项目(二)_unity 交换机仿真项目


文章目录

  • 前言
  • 一、Unity Plc 工业仿真项目是什么?
    • 为什么要做PLC工业仿真?
    • Unity 如何与 PLC 进行通信?
  • 二、效果展示
    • 1.PC端通信效果
    • 2.Web端通信效果
  • 三、项目地址
  • 四、主要代码介绍
    • 1.PlcManager
    • 2.PlcVariable
    • 3.PlcWebMessage
  • 五、如何使用及注意事项
  • 总结

前言

本项目结合了 S7.NetTIA Portal V16S7-PLCSIM Advanced V3.0,用于模拟 西门子 PLC 与Unity进行通信。实现制作工业仿真项目的数据通信功能:

1.CSV配置文件读取功能
2.Plc数据类型与C#数据类型互相转换
3.监听多个Plc数值变化
4.Plc数据的读取和写入
5.多个PLC创建和管理
6.支持PC/Web版本通讯


一、Unity Plc 工业仿真项目是什么?

Unity PLC 工业仿真项目 是指使用 Unity 引擎 结合 PLC(可编程逻辑控制器) 进行 工业自动化仿真 的项目。其核心目标是:
✅ 在PC端模拟工业设备的运行
✅ 使用PLC控制Unity内的3D场景和设备
✅ 实现虚拟工厂、HMI(人机界面)、流程仿真等功能

可以看看我上一篇文章的介绍: Unity开发记录_6_PC工业仿真项目 使用S7.Net 和 博图TIA Portal V16 和 S7-PLCSIM Advanced V3.0 和 Robot Studio 进行工业仿真

为什么要做PLC工业仿真?

在现实工业场景中,PLC广泛用于自动化控制,如工厂生产线、机器人控制、物流输送等。
但直接在真实设备上调试PLC程序,成本高、风险大,因此使用 Unity + PLC 进行仿真有如下优势:
✅ 降低设备损坏风险:不用在真实机器上调试,避免误操作导致损坏。
✅ 提高开发效率:可以在PC端快速测试PLC逻辑,无需等待硬件安装调试。
✅ 远程监控和培训:适用于工业培训和远程运维,可模拟不同的生产场景。

Unity 如何与 PLC 进行通信?

在 Unity + PLC 仿真项目中,Unity 需要与PLC进行 数据交互,常见的通信方式有:
S7.Net(C# 库,适用于西门子 S7 系列)
Modbus TCP(通用协议,适用于多种PLC品牌)
OPC UA(适用于工业物联网,数据交互标准)

二、效果展示

1.PC端通信效果

Unity开发记录_12_PC / Web工业仿真项目 使用S7.Net 和 博图TIA Portal V16 和 S7-PLCSIM Advanced V3.0 的开源项目(二)_unity 交换机仿真项目


2.Web端通信效果

Unity开发记录_12_PC / Web工业仿真项目 使用S7.Net 和 博图TIA Portal V16 和 S7-PLCSIM Advanced V3.0 的开源项目(二)_unity 交换机仿真项目


三、项目地址

GitHub 链接: UnityPlcProject_PC_Web
Unity开发记录_12_PC / Web工业仿真项目 使用S7.Net 和 博图TIA Portal V16 和 S7-PLCSIM Advanced V3.0 的开源项目(二)_unity 交换机仿真项目

四、主要代码介绍

1.PlcManager

封装所有Plc调用功能

using Newtonsoft.Json;using System;using System.Collections;using System.Collections.Generic;using System.Diagnostics;using System.Linq;using System.Threading.Tasks;using UnityEngine;using UnityEngine.Networking;using static ATF.MDataItem;using Debug = UnityEngine.Debug;namespace ATF{ ///  /// 3个不同的plc程序--示例 这个地方可以后期更改 ///  public enum EPlcName { EPN_FenJian, EPN_JiXieShou, EPN_CangKu, } ///  /// web端通讯的消息类型 ///  public enum DESType { DEST_PlcConnect,  //Plc 连接 DEST_DisPlcConnect,  //Plc 取消连接 DEST_AddListenerPLCValueChange, //监听Plc数据变化 DEST_RemoveListenerPLCValueChange, //移除Plc数据监听 DEST_WriteValue,  //写入数值 DEST_IsConnected,  //是否连接 DEST_ReadValue,  //读取数值 } ///  /// Plc管理单例,所有Plc相关的操作 都从这调用 ///  public class PlcManager : UnitySingletonTemplate<PlcManager> { private int m_plcUpdateSpeed = 100;//Plc数据更新的速率: 100ms private Dictionary<EPlcName, PlcVariable> m_plcDic = new Dictionary<EPlcName, PlcVariable>(); private Process m_webPlcProcess; //[DllImport(\"__Internal\")] //private static extern void OpenExe(string path,string args); private async void Start() { m_plcDic.Add(EPlcName.EPN_FenJian, new PlcVariable(await ReadPLCData(PlcConstant.PlcConfigDataPath_FenJian), m_plcUpdateSpeed)); m_plcDic.Add(EPlcName.EPN_JiXieShou, new PlcVariable(await ReadPLCData(PlcConstant.PlcConfigDataPath_JiXieShou), m_plcUpdateSpeed)); m_plcDic.Add(EPlcName.EPN_CangKu, new PlcVariable(await ReadPLCData(PlcConstant.PlcConfigDataPath_CangKu), m_plcUpdateSpeed));#if UNITY_WEBGL#if UNITY_EDITOR m_webPlcProcess = new Process(); m_webPlcProcess.StartInfo = new ProcessStartInfo(Application.streamingAssetsPath + PlcConstant.WebPlcServerPath, Application.streamingAssetsPath); m_webPlcProcess.Start();#else //Debug.Log(\"开始调用js代码启动外部服务器...\"); //string streamingAssetsPath = Application.streamingAssetsPath.TrimEnd(\'/\'); //Debug.Log(\"streamingAssetsPath: \" + streamingAssetsPath); //string webPlcPath = streamingAssetsPath + \"/\" + PlcConstant.WebPlcServerPath.TrimStart(\'/\'); //Debug.Log(\"webPlcPath: \" + webPlcPath); //string args = Uri.EscapeDataString(Application.streamingAssetsPath); //string finalUrl = webPlcPath + \"?args=\" + args; //Debug.Log(\"finalUrl: \" +finalUrl); //OpenExe(webPlcPath, streamingAssetsPath);#endif ThreadUtility.Ins.CreateThread(async () => { while (true) {  MainThreadTaskQueue.EnqueueTask(async () =>  { await StartUpdateReadPlcValeAsync();  });  await Task.Delay(m_plcUpdateSpeed); } });#endif } private void OnDestroy() { Process[] processes = Process.GetProcesses(); foreach (Process process in processes) { try {  if (m_webPlcProcess != null && !process.HasExited && process.ProcessName == m_webPlcProcess.ProcessName)  { process.Kill();  } } catch (InvalidOperationException ex) {  UnityEngine.Debug.Log(ex); } } } #region PLC函数 ///  /// 开始连接PLC ///  ///  ///  ///  ///  public void StartConnectPlc(EPlcName plcName, string plcIp = \"\", Action succeedAction = null, Action errorAction = null) { if (!m_plcDic.ContainsKey(plcName)) { errorAction?.Invoke(); return; }#if UNITY_WEBGL MSG_PlcConnect requestData = new MSG_PlcConnect { plcName = plcName, plcIp = plcIp, message = $\"S2C-> PLC{plcName} --- {plcIp} 正在连接..\" }; string jsonData = JsonConvert.SerializeObject(requestData); StartCoroutine(PlcWebMessage.Ins.C2S_StartSendMessage(DESType.DEST_PlcConnect, PlcConstant.PlcConnectServerPath, jsonData, succeedAction, errorAction));#else m_plcDic[plcName].StartConnect(plcIp, succeedAction, errorAction); m_plcDic[plcName].StartUpdateWritePlc();#endif } ///  /// 开始更新读取的Plc数值 ///  ///  private async Task StartUpdateReadPlcValeAsync() { foreach (var plc in m_plcDic) { if (plc.Value != null && await IsConnected(plc.Key)) {  foreach (var valName in plc.Value.m_plcOutputList)  { if (plc.Value.m_plcDic[valName].Value != null && !plc.Value.m_plcDic[valName].SingleWrite) { WriteValue(plc.Key, valName, plc.Value.m_plcDic[valName].Value); }  }  Dictionary<string, object> newDic = await StartReadValue(plc.Key);  //foreach (var val in newDic)  //{  // Debug.Log($\"{plc.Key}接受的信息:{val.Key},{val.Value}\");  //}  foreach (var valName in plc.Value.m_plcInputList)  { if (plc.Value.m_plcDic.ContainsKey(valName)) { plc.Value.m_plcDic[valName].Value = newDic[valName]; }  } } } } ///  /// 开始取消连接PLC ///  ///  ///  ///  public void StartDisConnectPlc(EPlcName plcName, Action succeedAction = null, Action errorAction = null) { if (!m_plcDic.ContainsKey(plcName)) { errorAction?.Invoke(); return; }#if UNITY_WEBGL MSG_DisPlcConnect requestData = new MSG_DisPlcConnect { message = $\"C2S-> PLC{plcName} --- 正在取消连接..\" }; string jsonData = JsonConvert.SerializeObject(requestData); StartCoroutine(PlcWebMessage.Ins.C2S_StartSendMessage(DESType.DEST_DisPlcConnect, PlcConstant.PlcConnectServerPath, jsonData, succeedAction, errorAction));#else m_plcDic[plcName].StartDisConnect(succeedAction, errorAction);#endif } ///  /// 开始监听PLC值改变 ///  ///  ///  ///  public void AddListenerPLCValueChange(EPlcName plcName, string valName, PlcValueChanged action) { if (!m_plcDic.ContainsKey(plcName)) { return; } m_plcDic[plcName].AddListenerPLCValueChange(valName, action); } ///  /// 移除PLC值改变 ///  ///  ///  ///  public void RemoveListenerPLCValueChange(EPlcName plcName, string valName, PlcValueChanged action) { if (!m_plcDic.ContainsKey(plcName)) { return; } m_plcDic[plcName].RemoveListenerPLCValueChange(valName, action); } ///  /// 写入值 ///  ///  ///  public void WriteValue(EPlcName plcName, string valName, object val, bool singleWrite = false) { if (!m_plcDic.ContainsKey(plcName)) { return; }#if UNITY_WEBGL MSG_WriteValue requestData = new MSG_WriteValue { plcName = plcName, valName = valName, val = val, singleWrite = singleWrite, message = $\"C2S-> PLC:{plcName} ---Val:{valName}\" }; string jsonData = JsonConvert.SerializeObject(requestData); StartCoroutine(PlcWebMessage.Ins.C2S_StartSendMessage(DESType.DEST_WriteValue, PlcConstant.PlcConnectServerPath, jsonData));#else m_plcDic[plcName].WriteValue(valName, val, singleWrite);#endif } ///  /// 开始读取数值 ///  ///  ///  private async Task<Dictionary<string, object>> StartReadValue(EPlcName plcName) {#if UNITY_WEBGL MSG_ReadValue requestData = new MSG_ReadValue { plcName = plcName, message = $\"C2S-> PLC{plcName} --- 正在读取数据..\" }; MSG_Type msg_Type = new MSG_Type { desType = DESType.DEST_ReadValue, content = JsonConvert.SerializeObject(requestData), }; string jsonData = JsonConvert.SerializeObject(msg_Type); return await PlcWebMessage.Ins.C2S_ReadValue(PlcConstant.PlcConnectServerPath, jsonData);#endif return null; } ///  /// 是否连接 ///  ///  ///  public async Task<bool> IsConnected(EPlcName plcName) { if (!m_plcDic.ContainsKey(plcName)) { return false; }#if UNITY_WEBGL MSG_IsConnected requestData = new MSG_IsConnected { plcName = plcName, message = $\"C2S-> PLC:{plcName}\" }; MSG_Type msg_Type = new MSG_Type { desType = DESType.DEST_IsConnected, content = JsonConvert.SerializeObject(requestData), }; string jsonData = JsonConvert.SerializeObject(msg_Type); return await PlcWebMessage.Ins.C2S_IsConnected(DESType.DEST_IsConnected, PlcConstant.PlcConnectServerPath, jsonData);#else return m_plcDic[plcName].IsConnected();#endif } ///  /// 读取配置文件 ///  ///  ///  public async Task<Dictionary<string, MDataItem>> ReadPLCData(string csvPath) { csvPath = Application.streamingAssetsPath + csvPath;#if UNITY_WEBGL && !UNITY_EDITOR csvPath = csvPath.Replace(\"file://\", \"\"); #endif return await PlcCSVUtility.ReadPLCData(csvPath); } #endregion private IEnumerator WaitForServer() { bool isServerRunning = false; while (!isServerRunning) { using (UnityWebRequest request = UnityWebRequest.Get(\"http://localhost:5001\")) {  yield return request.SendWebRequest();  if (request.result == UnityWebRequest.Result.Success)  { isServerRunning = true; Debug.Log(\"Server is running. Proceeding with exe start.\"); string exePath = Application.streamingAssetsPath + PlcConstant.WebPlcServerPath; string arg1 = Application.streamingAssetsPath + PlcConstant.WebPlcServerPath; string arg2 = Application.streamingAssetsPath; string[] exeArgs = new string[] { arg1, arg2 }; StartCoroutine(StartWebServerHelperExe(exePath, exeArgs));  }  else  { Debug.Log(\"Waiting for server to start...\"); yield return new WaitForSeconds(1);} } } } ///  /// 开始webserver辅助程序 ///  ///  ///  private IEnumerator StartWebServerHelperExe(string exePath, string[] exeArgs) { string parametersJson = string.Join(\",\", exeArgs.Select(arg => \"\\\"\" + arg + \"\\\"\")); string jsonBody = \"{\\\"ExePath\\\":\\\"\" + exePath + \"\\\", \\\"Parameters\\\":[\" + parametersJson + \"]}\"; using (UnityWebRequest request = new UnityWebRequest(PlcConstant.WebPlcServerHelperUrl, \"POST\")) { byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(jsonBody); request.uploadHandler = new UploadHandlerRaw(bodyRaw); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader(\"Content-Type\", \"application/json\"); yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) {  Debug.Log(\"Exe started successfully with parameters!\"); } else {  Debug.LogError(\"Failed to start exe: \" + request.error); } } } }}

2.PlcVariable

封装所有Plc内部功能

using S7.Net;using System;using static WebPlc.Scripts.MDataItem;namespace WebPlc.Scripts{ public class MDataItem { public string Id = \"\"; public string Name = \"\"; public DataType DataType = DataType.Input; public VarType VarType = VarType.Byte; public string LogicalAddress = \"\"; public bool SingleWrite = false; private object? m_value = null; private readonly object m_lock = new object(); public event PlcValueChanged? OnMyValueChanged = null; public MDataItem() { } public object Value { get { lock (m_lock) {  return m_value; } } set { if (value == null) {  return; } lock (m_lock) {  if (!value.Equals(m_value))  { m_value = value; OnMyValueChanged?.Invoke(m_value);  } } } } public delegate void PlcValueChanged(object sender); public void SpecialTrigger() { OnMyValueChanged?.Invoke(m_value); } } internal class PlcVariable { private Plc m_plc = null; private string m_plcIp = \"192.168.0.1\"; private CpuType m_plcType = CpuType.S71200; private short m_plcJiJiaNumber = 0; private short m_plcJiCaoNumber = 1; private int m_plcUpdateSpeed = 100; private int m_plcWriteSpeed = 1000; private bool m_plcUpdateState = true; public readonly Dictionary<string, MDataItem> m_plcDic = new Dictionary<string, MDataItem>(); public readonly List<string> m_plcInputList = new List<string>(); public readonly List<string> m_plcOutputList = new List<string>(); public PlcVariable(Dictionary<string, MDataItem> plcDic, int plcUpdateSpeed = 100) { m_plcDic.Clear(); m_plcInputList.Clear(); m_plcOutputList.Clear(); m_plcUpdateSpeed = plcUpdateSpeed; m_plcDic = plcDic; InitDataItemList(); } public async Task StartConnect(string ip, Action succeedAction = null, Action errorAction = null) { try { Console.WriteLine(\"PLC准备连接IP: \" + ip); if (!string.IsNullOrEmpty(ip)) {  m_plcIp = ip;  Console.WriteLine($\"当前PLC尝试开始连接PLC: {m_plcType}, {m_plcIp}, {m_plcJiJiaNumber}, {m_plcJiCaoNumber}\");  m_plc = new Plc(m_plcType, m_plcIp, m_plcJiJiaNumber, m_plcJiCaoNumber); } else {  Console.WriteLine($\"当前输入IP为空,尝试开始连接默认IP,PLC: {m_plcType}, {m_plcIp}, {m_plcJiJiaNumber}, {m_plcJiCaoNumber}\");  m_plc = new Plc(m_plcType, m_plcIp, m_plcJiJiaNumber, m_plcJiCaoNumber); } await m_plc.OpenAsync().ContinueWith(t => {  if (t.IsFaulted)  { Task.Delay(100).ContinueWith(_ => { errorAction?.Invoke(); }); Console.WriteLine(\"连接PLC失败\");  }  else if (t.IsCompleted)  { Task.Delay(100).ContinueWith(_ => { succeedAction?.Invoke(); }); SetPlcUpdateState(true); ThreadUtility.Ins.CreateThread(() => { while (true) { UpdateReadeValue(); Thread.Sleep(m_plcUpdateSpeed); } }); Console.WriteLine(\"连接PLC成功\");  }  Console.WriteLine(\"连接回调: \" + t.Exception); }); } catch (System.Exception e) { errorAction?.Invoke(); Console.WriteLine(\"PLC连接报错:\" + e); } } ///  /// 开始取消连接 ///  ///  ///  public void StartDisConnect(Action succeedAction = null, Action errorAction = null) { try { if (m_plc != null && m_plc.IsConnected) {  m_plc.Close();  succeedAction?.Invoke(); } else {  errorAction?.Invoke(); } } catch (Exception e) { errorAction?.Invoke(); throw e; } } ///  /// 初始化DataItem列表 ///  private void InitDataItemList() { foreach (var item in m_plcDic.Values) { if (item.DataType == DataType.Input) {  m_plcInputList.Add(item.Id + \"_\" + item.Name); } else {  m_plcOutputList.Add(item.Id + \"_\" + item.Name); } } } ///  /// 更新读取值 ///  private async void UpdateReadeValue() { if (m_plcUpdateState && m_plc != null && m_plc.IsConnected) { foreach (var plcName in m_plcInputList) {  if (m_plcDic.ContainsKey(plcName))  { m_plcDic[plcName].Value = await m_plc.ReadAsync(m_plcDic[plcName].LogicalAddress);  } } } } ///  /// 得到值 ///  ///  ///  public object GetValue(string valName) { if (m_plc != null && m_plc.IsConnected && m_plcDic.ContainsKey(valName)) { return m_plcDic[valName].Value; } return null; } ///  /// 写入值 ///  ///  ///  public void WriteValue(string valName, object val, bool singleWrite = false) { if (m_plcDic.ContainsKey(valName)) { m_plcDic[valName].Value = val; m_plcDic[valName].SingleWrite = singleWrite; if (m_plc != null && m_plc.IsConnected) {  string address = m_plcDic[valName].LogicalAddress;  switch (m_plcDic[valName].VarType)  { case VarType.Bit: break; case VarType.Byte: //Console.WriteLine($\"开始{address} 写 {valName} 值: {(bool)val}\"); m_plc.WriteAsync(address, Convert.ToBoolean(val)); break; case VarType.Word: break; case VarType.DWord: break; case VarType.Int: m_plc.WriteAsync(address, Convert.ToInt16(val)); break; case VarType.DInt: break; case VarType.Real: //m_plc.WriteAsync(address, Conversion.ConvertToFloat((uint)val)); try { m_plc.WriteAsync(address, Convert.ToSingle(val)); } catch (Exception) { Console.WriteLine($\"报错:m_plc.WriteAsync(address, (float)val):{val}\"); }  break; case VarType.LReal: break; case VarType.String: break; case VarType.S7String: break; case VarType.S7WString: break; case VarType.Timer: break; case VarType.Counter: break; case VarType.DateTime: break; case VarType.DateTimeLong: break; default: break;  } } } } ///  /// 设置Plc更新 ///  ///  private void SetPlcUpdateState(bool state) { m_plcUpdateState = state; } ///  /// 开始监听数据变化 ///  ///  ///  public void AddListenerPLCValueChange(string valName, PlcValueChanged action) { if (m_plcDic.ContainsKey(valName)) { m_plcDic[valName].OnMyValueChanged += action; //Console.WriteLine($\"当前监听变量:{valName}\"); } } ///  /// 结束监听数据变化 ///  ///  ///  public void RemoveListenerPLCValueChange(string valName, PlcValueChanged action) { if (m_plcDic.ContainsKey(valName)) { m_plcDic[valName].OnMyValueChanged -= action; } } ///  /// 特殊触发只读数据 ///  public void TriggerAllReadValue() { if (m_plc != null && m_plc.IsConnected) { foreach (var plcName in m_plcInputList) {  m_plcDic[plcName].SpecialTrigger(); } } } ///  /// 开始更新输出数据 ///  public void StartUpdateWritePlc() { ThreadUtility.Ins.CreateThread(() => { while (true) {  if (m_plc != null && m_plc.IsConnected)  { foreach (var plcName in m_plcOutputList) { if (m_plcDic[plcName].Value != null && !m_plcDic[plcName].SingleWrite) { WriteValue(plcName, m_plcDic[plcName].Value, m_plcDic[plcName].SingleWrite); //Console.WriteLine($\"持续写出值:{plcName} {m_plcDic[plcName].Value}\"); } }  }  Thread.Sleep(m_plcWriteSpeed); } }); } public bool IsConnected() { if (m_plc != null) { return m_plc.IsConnected; } return false; } public async Task<object?> ReadPlcDataAsync(string valName) { if (!m_plcDic.ContainsKey(valName)) { return null; } return await m_plc.ReadAsync(m_plcDic[valName].LogicalAddress); } }}

3.PlcWebMessage

Plc Webgl平台通讯代码

using Newtonsoft.Json;using System;using System.Collections;using System.Collections.Generic;using System.Text;using System.Threading.Tasks;using UnityEngine;using UnityEngine.Networking;namespace ATF{ [Serializable] public class MSG_Type { public DESType desType = DESType.DEST_IsConnected; public string content; } [Serializable] public class MSG_PlcConnect { public EPlcName plcName = EPlcName.EPN_JiXieShou; public string plcIp = \"\"; //Plc IP public bool plcState; //Plc状态 public string message = \"\"; //返回信息 } [Serializable] public class MSG_DisPlcConnect { public EPlcName plcName = EPlcName.EPN_JiXieShou; public bool connectState; //连接状态 public string message = \"\"; //返回信息 } [Serializable] public class MSG_AddListenerPLCValueChange { public EPlcName plcName = EPlcName.EPN_JiXieShou; public string valName = \"\"; public string message = \"\"; //返回信息 } [Serializable] public class MSG_WriteValue { public EPlcName plcName = EPlcName.EPN_JiXieShou; public string valName = \"\"; public object val = \"\"; public bool singleWrite = false; public string message = \"\"; //返回信息 } [Serializable] public class MSG_IsConnected { public EPlcName plcName = EPlcName.EPN_JiXieShou; public bool isConnected ; public string message = \"\"; //返回信息 } [Serializable] public class MSG_ReadValue { public EPlcName plcName = EPlcName.EPN_JiXieShou; public Dictionary<string, object> dataDic = new Dictionary<string, object>(); //返回信息 public string message = \"\"; } public class PlcWebMessage : CommonSingletonTemplate<PlcWebMessage> { public IEnumerator C2S_StartSendMessage(DESType desType,string url, string jsonData, Action succeedAction = null, Action errorAction = null) { using (UnityWebRequest request = new UnityWebRequest(url, \"POST\")) { MSG_Type msg_Type = new MSG_Type() {  desType = desType,  content = jsonData, }; string msg = JsonConvert.SerializeObject(msg_Type); byte[] bodyRaw = Encoding.UTF8.GetBytes(msg); request.uploadHandler = new UploadHandlerRaw(bodyRaw); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader(\"Content-Type\", \"application/json\"); yield return request.SendWebRequest(); if (request.ATResult()) {  string responseJson = request.downloadHandler.text;  MSG_Type processRequestType = JsonConvert.DeserializeObject<MSG_Type>(responseJson);  if (processRequestType != null)  { switch (processRequestType.desType) { case DESType.DEST_PlcConnect: C2S_PlcConnect(processRequestType.content, succeedAction, errorAction); break; case DESType.DEST_DisPlcConnect: C2S_DisPlcConnect(processRequestType.content, succeedAction, errorAction); break; case DESType.DEST_AddListenerPLCValueChange: //DES_AddListenerPLCValueChange(processRequestType.content); break; case DESType.DEST_RemoveListenerPLCValueChange: //DES_RemoveListenerPLCValueChange(processRequestType.content); break; case DESType.DEST_WriteValue: //DES_WriteValue(processRequestType.content); break; case DESType.DEST_IsConnected: //C2S_IsConnected(responseJson, succeedAction, errorAction); break; default: break; }  } } else {  Debug.LogError($\"Request请求失败: {request.error}\");  errorAction?.Invoke(); } } } ///  /// 是否建立了连接 ///  ///  ///  ///  ///  ///  ///  public async Task<bool> C2S_IsConnected(DESType desType, string url, string jsonData, Action succeedAction = null, Action errorAction = null) { using (UnityWebRequest request = new UnityWebRequest(url, \"POST\")) { byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonData); request.uploadHandler = new UploadHandlerRaw(bodyRaw); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader(\"Content-Type\", \"application/json\"); var operation = request.SendWebRequest(); while (!operation.isDone) {  await Task.Yield();  } if (request.ATResult()) {  string responseJson = request.downloadHandler.text;  MSG_Type processRequestType = JsonConvert.DeserializeObject<MSG_Type>(responseJson);  try  { if (processRequestType != null) { switch (processRequestType.desType) { case DESType.DEST_IsConnected:  MSG_IsConnected responseData = JsonConvert.DeserializeObject<MSG_IsConnected>(processRequestType.content);  return responseData.isConnected; default:  break; } }  }  catch (Exception ex)  { Debug.LogError($\"JSON 解析错误: {ex.Message}\");  } } else {  Debug.LogError($\"请求失败: {request.error}\"); } } return false; } ///  /// 读取值 ///  ///  ///  ///  ///  public async Task<Dictionary<string, object>> C2S_ReadValue(string url, string jsonData) { using (UnityWebRequest request = new UnityWebRequest(url, \"POST\")) { byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonData); request.uploadHandler = new UploadHandlerRaw(bodyRaw); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader(\"Content-Type\", \"application/json\"); var operation = request.SendWebRequest(); while (!operation.isDone) {  await Task.Yield();  } if (request.ATResult())  {  string responseJson = request.downloadHandler.text;  MSG_Type processRequestType = JsonConvert.DeserializeObject<MSG_Type>(responseJson);  try  { if (processRequestType != null) { switch (processRequestType.desType) { case DESType.DEST_ReadValue:  MSG_ReadValue responseData = JsonConvert.DeserializeObject<MSG_ReadValue>(processRequestType.content);  //Debug.Log($\"C2S_ReadValue Dic Count->{responseData.dataDic.Count}\");  return responseData.dataDic; default:  break; } }  }  catch (Exception ex)  { Debug.LogError($\"JSON 解析错误: {ex.Message}\");  } } else {  Debug.LogError($\"请求失败: {request.error}\"); } } return null; // 失败时返回默认值 } ///  /// 消息_解析Plc连接 ///  ///  ///  ///  private void C2S_PlcConnect(string content, Action succeedAction = null, Action errorAction = null) { MSG_PlcConnect responseData = JsonConvert.DeserializeObject<MSG_PlcConnect>(content); if (responseData.plcState) { Debug.Log($\"服务器响应: {responseData.message},状态:{responseData.plcState}”\"); succeedAction?.Invoke(); } else { Debug.LogError($\"请求失败: {responseData.plcState}\"); errorAction?.Invoke(); } } ///  /// 消息_取消连接 ///  ///  ///  ///  private void C2S_DisPlcConnect(string request, Action succeedAction = null, Action errorAction = null) { MSG_DisPlcConnect responseData = JsonConvert.DeserializeObject<MSG_DisPlcConnect>(request); if (responseData.connectState) { Debug.Log($\"服务器响应: {responseData.message}\"); succeedAction?.Invoke(); } else { Debug.LogError($\"请求失败: {responseData.connectState}\"); errorAction?.Invoke(); } } ///  /// 消息_开始监听PLC值变化 ///  ///  ///  ///  private void C2S_AddListenerPLCValueChange(string request, Action succeedAction = null, Action errorAction = null) { MSG_AddListenerPLCValueChange responseData = JsonConvert.DeserializeObject<MSG_AddListenerPLCValueChange>(request); //if (responseData.plcValueChangedState) //{ // Debug.Log($\"服务器响应: {responseData.message}\"); // succeedAction?.Invoke(); //} //else //{ // Debug.LogError($\"请求失败: {request.error}\"); // errorAction?.Invoke(); //} } ///  /// 消息_开始移除PLC值变化 ///  ///  ///  ///  private void C2S_RemoveListenerPLCValueChange(string request, Action succeedAction = null, Action errorAction = null) { MSG_AddListenerPLCValueChange responseData = JsonConvert.DeserializeObject<MSG_AddListenerPLCValueChange>(request); //if (responseData.plcValueChangedState) //{ // Debug.Log($\"服务器响应: {responseData.message}\"); // succeedAction?.Invoke(); //} //else //{ // Debug.LogError($\"请求失败: {request.error}\"); // errorAction?.Invoke(); //} } ///  /// 消息_开始移除PLC值变化 ///  ///  ///  ///  private void C2S_WriteValue(string request, Action succeedAction = null, Action errorAction = null) { MSG_WriteValue responseData = JsonConvert.DeserializeObject<MSG_WriteValue>(request); //if (responseData.plcValueChangedState) //{ // Debug.Log($\"服务器响应: {responseData.message}\"); // succeedAction?.Invoke(); //} //else //{ // Debug.LogError($\"请求失败: {request.error}\"); // errorAction?.Invoke(); //} } }}

五、如何使用及注意事项

1.安装 Unity、TIA Portal V16、S7-PLCSIM Advanced V3.0
2.新建博图项目,配置好Plc点位
3.配置好 Assets/StreamingAssets/PlcConfig/Plc程序A.csv路径下的Plc点位
4.输入Plc 项目IP,点击连接
5.Web平台格外注意:先打开Assets/StreamingAssets/WebPlc/WebPlc.exe路径下的服务器,再进行连接操作。

总结

好啦,本文介绍的就这么多,有兴趣可以下载看看。Unity&Plc仿真项目,上手门槛比较低,但是前提是要有相关软件的使用基础,比如如何使用博图,如何使用S7 - PLCSIM Advanced V3.0,如何使用RobotStudio等。本文介绍的通信和管理Plc只是其中一环,实际项目中有很大一部分工作在于实现真实物理效果的仿真。后期如果真的去做仿真项目,希望本文实现的功能能给大家做个参考,其实直接在这个基础上开发新功能也是可以的,毕竟我封装的目的就是为了便于二次开发使用。后续这个项目会持续更新,会考虑将Abb相关的功能也加进来。喜欢的小伙伴可以点赞关注加收藏,你的支持是我更新的最大动力,有问题可以滴我~