如何通过Python Socket通信控制Unity多对象?问题排查
问题排查:Python Socket发送位置数据至Unity无响应,多对象控制需求
我尝试用Python读取CSV位置数据,通过Socket发送到Unity控制多个对象位置。Python端显示数据已发送,但Unity完全没反应。之前Unity报过SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted,改了地址后没再报错,但单对象控制正常,现在想用单个脚本按ID控制14个对象,不想拆成多个脚本。
Python发送代码
import socket import time host, port = "127.0.0.1", 25001 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Allow reuse of the port sock.connect((host, port)) startPos1 = [0, 0, 0] # Initial position for Cylinder1 startPos2 = [0, 0, 0] # Initial position for Cylinder2 try: while True: time.sleep(0.5) # Sleep 0.5 sec # Update positions startPos1[0] += 0.1 # Increase x by 0.1 for Cylinder1 startPos2[1] += 0.1 # Increase y by 0.1 for Cylinder2 # Convert positions to string format expected by Unity posString1 = '(' + ','.join(map(str, startPos1)) + ')' posString2 = '(' + ','.join(map(str, startPos2)) + ')' posString = f"pos1:{posString1};pos2:{posString2}" print(posString) # Send position string to Unity sock.sendall(posString.encode("UTF-8")) # Receive and print the response from Unity receivedData = sock.recv(1024).decode("UTF-8") print(receivedData) finally: sock.close() # Ensure the socket is closed when done
Unity接收代码
using System.Collections; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Text; using UnityEngine; using System.Threading; public class CSharpForGIT : MonoBehaviour { Thread mThread; public string connectionIP = "127.0.0.1"; public int connectionPort = 25001; IPAddress localAdd; TcpListener listener; TcpClient client; public GameObject cylinder1; public GameObject cylinder2; Vector3 receivedPos1 = Vector3.zero; Vector3 receivedPos2 = Vector3.zero; private object lockObject = new object(); bool running; private void Update() { lock (lockObject) { if (cylinder1 != null) { cylinder1.transform.position = receivedPos1; } if (cylinder2 != null) { cylinder2.transform.position = receivedPos2; } } } private void Start() { ThreadStart ts = new ThreadStart(GetInfo); mThread = new Thread(ts); mThread.Start(); } private void OnApplicationQuit() { running = false; if (client != null) client.Close(); if (listener != null) listener.Stop(); if (mThread != null) mThread.Abort(); } void GetInfo() { try { localAdd = IPAddress.Parse(connectionIP); listener = new TcpListener(IPAddress.Any, connectionPort); listener.Start(); client = listener.AcceptTcpClient(); running = true; while (running) { SendAndReceiveData(); } } catch (SocketException ex) { Debug.Log("Socket exception: " + ex.ToString()); } finally { listener.Stop(); } } void SendAndReceiveData() { NetworkStream nwStream = client.GetStream(); byte[] buffer = new byte[client.ReceiveBufferSize]; int bytesRead = nwStream.Read(buffer, 0, client.ReceiveBufferSize); string dataReceived = Encoding.UTF8.GetString(buffer, 0, bytesRead); if (!string.IsNullOrEmpty(dataReceived)) { ProcessReceivedData(dataReceived); byte[] myWriteBuffer = Encoding.ASCII.GetBytes("Hey I got your message Python! Do You see this message?"); nwStream.Write(myWriteBuffer, 0, myWriteBuffer.Length); } } void ProcessReceivedData(string data) { string[] positions = data.Split(';'); lock (lockObject) { foreach (var pos in positions) { if (pos.StartsWith("pos1:")) { receivedPos1 = StringToVector3(pos.Substring(5)); } else if (pos.StartsWith("pos2:")) { receivedPos2 = StringToVector3(pos.Substring(5)); } } } } public static Vector3 StringToVector3(string sVector) { if (sVector.StartsWith("(") && sVector.EndsWith(")")) { sVector = sVector.Substring(1, sVector.Length - 2); } string[] sArray = sVector.Split(','); Vector3 result = new Vector3( float.Parse(sArray[0]), float.Parse(sArray[1]), float.Parse(sArray[2])); return result; } }
排查步骤
- 确认连接顺序:必须先启动Unity(让TcpListener先监听端口),再运行Python客户端。如果顺序反了,Python会连接失败,Unity后续监听也会触发端口占用报错。
- 检查端口占用:用系统命令(Windows:
netstat -ano | findstr 25001;Linux/macOS:lsof -i :25001)确认端口25001未被其他程序占用,这是之前报错的核心原因。 - 验证数据接收完整性:Unity的
nwStream.Read()可能分段接收数据,导致解析失败。在Unity的SendAndReceiveData中打印dataReceived,确认是否是完整的pos1:(x,y,z);pos2:(x,y,z)格式。如果分段,可在Python发送的字符串末尾加\n作为终止符,Unity端循环读取直到收到终止符再解析。 - 检查变量更新与锁机制:在
ProcessReceivedData中打印解析后的Vector3值,确认receivedPos1/receivedPos2是否正确赋值;同时在Update中打印对象位置,验证是否同步更新。 - 确认GameObject引用:在Unity编辑器中,确保脚本的
cylinder1和cylinder2已正确绑定对应对象,不为空。
多对象控制优化方案(单脚本控制14个对象)
核心思路:用字典管理对象,按ID匹配更新
Unity脚本修改
- 添加字典存储对象,按ID映射:
public Dictionary<string, GameObject> controlledObjects = new Dictionary<string, GameObject>(); - 在
Start方法中初始化字典(示例按对象名称批量获取):private void Start() { // 批量获取名称为Cylinder1到Cylinder14的对象 for (int i = 1; i <= 14; i++) { GameObject obj = GameObject.Find($"Cylinder{i}"); if (obj != null) { controlledObjects.Add($"pos{i}", obj); } } // 启动监听线程 ThreadStart ts = new ThreadStart(GetInfo); mThread = new Thread(ts); mThread.Start(); } - 修改
ProcessReceivedData方法,通用解析所有ID的位置:void ProcessReceivedData(string data) { string[] positions = data.Split(';'); lock (lockObject) { foreach (var pos in positions) { if (string.IsNullOrEmpty(pos)) continue; int colonIndex = pos.IndexOf(':'); if (colonIndex <= 0) continue; // 拆分ID和位置字符串 string objId = pos.Substring(0, colonIndex); string posStr = pos.Substring(colonIndex + 1); // 查找对应对象并更新位置 if (controlledObjects.ContainsKey(objId)) { Vector3 newPos = StringToVector3(posStr); controlledObjects[objId].transform.position = newPos; } } } }
- 添加字典存储对象,按ID映射:
Python端修改
- 循环生成14个对象的位置字符串,无需单独编写每个对象的代码,示例:
# 初始化14个对象的初始位置 startPositions = [[0,0,0] for _ in range(14)] try: while True: time.sleep(0.5) # 更新所有对象位置(示例:每个对象x轴递增0.1) for i in range(14): startPositions[i][0] += 0.1 # 拼接所有位置字符串 posParts = [] for i in range(14): posStr = f"({','.join(map(str, startPositions[i]))})" posParts.append(f"pos{i+1}:{posStr}") posString = ';'.join(posParts) print(posString) sock.sendall(posString.encode("UTF-8")) receivedData = sock.recv(1024).decode("UTF-8") print(receivedData) finally: sock.close()
- 循环生成14个对象的位置字符串,无需单独编写每个对象的代码,示例:
内容的提问来源于stack exchange,提问作者Vintertid
相关产品推荐
相关产品推荐

