You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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匹配更新

  1. 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;
                  }
              }
          }
      }
      
  2. 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()
      

内容的提问来源于stack exchange,提问作者Vintertid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 10:27:33