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

Unity通过UDP接收Anylogic数据后无法更新数据的问题

问题

原本Unity可通过UDP接收Anylogic的数据,给Anylogic添加某个库后出现数据接收不全的情况。调整UDPClient脚本中ReceiveData函数的Debug输出位置后,Unity的Debug显示已完整接收Anylogic发送的数据,但Unity内的数据仍无法更新。

附Unity端UDP通信代码

using UnityEngine;
using System.Collections;
    
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;
using System.Globalization;
using UnityEditor;
using static JSONReader;

[Serializable]
public class RealTimeItem
{
    public GameObject Object;
    public int NetID;
    public Vector3 Position;
    public Vector3 Target;
    public float TimeReach;
    public float TimeReachNext;
    public bool change;

    public Agente agent;
}

public class UDPClient : MonoBehaviour
{
    public List<RealTimeItem> items;

    public static UDPClient instance;
    public int portListen = 4444;
    public string ipSend = "";
    public int portSend = 5555;

    public GameObject[]  notifyObjects;
    public string messageToNotify;

    private string received = "";
    
    private UdpClient client;
    private Thread receiveThread;
    private IPEndPoint remoteEndPoint;
    private IPAddress ipAddressSend;

    public float TimeStamp = 1;

    public bool SetData(Agente ag)
    {
        if (getObjWithID(ag.id) == null) 
            return false;

        RealTimeItem itemUse = items[GetID(ag.id)];
        int id = Convert.ToInt32(ag.id);

        GameObject obj = getObjWithID(id);

        /*
        if (ag.coordenadas.Count > (ag.coordenadas.Count - 2))
        { 
            itemUse.Position = ag.coordenadas[ag.coordenadas.Count - 2]; 
        }
        */

        itemUse.Position = ag.coordenadas[ag.coordenadas.Count - 1];
        itemUse.Target = ag.coordenadas[ag.coordenadas.Count - 1];

        itemUse.TimeReach = ag.tiempos[ag.tiempos.Count - 1];
        itemUse.TimeReachNext = ag.tiempos[ag.tiempos.Count - 1];
        itemUse.change = true;

        // Debug.LogError("Checking: " + itemUse.NetID + "|||AGID: " + ag.id);
        //  obj.transform.position = itemUse.Position;
        return true;
    }

    private void FixedUpdate()
    {
        foreach(var xItem in items)
        {
            //  xItem.Object.transform.position = Vector3.MoveTowards(xItem.Object.transform.position, xItem.Position, TimeStamp * Time.deltaTime);
            //  xItem.Object.transform.LookAt(xItem.Target);
        }
    }

    public int GetID(int SearchID)
    {
        foreach (var item in items)
        {
            if (item.NetID.Equals(SearchID)) 
                return items.IndexOf(item);
        }

        return 0;
    }

    public GameObject getObjWithID(int SearchID)
    {
        foreach (var item in items)
        {
            if (item.NetID.Equals(SearchID)) 
                return item.Object;
        }

        return null;
    }

    public void Awake ()
    {
        instance = this;

        //Check if the ip address entered is valid. If not, sendMessage will broadcast to all ip addresses 
        IPAddress ip;

        if (IPAddress.TryParse (ipSend, out ip)) 
        {
            remoteEndPoint = new IPEndPoint (ip, portSend);
        } 
        else 
        {
            remoteEndPoint = new IPEndPoint (IPAddress.Broadcast, portSend);
        }

        //Initialize client and thread for receiving

        client = new UdpClient (portListen);

        receiveThread = new Thread (new ThreadStart (ReceiveData));
        receiveThread.IsBackground = true;
        receiveThread.Start ();
    }

    void Update ()
    {
        // Check if a message has been received
        if (received != "")
        {
            //Debug.Log("UDPClient: message received \'" + received + "\'");

            // Notify each object defined in the array with the message received
            foreach (GameObject g in notifyObjects)
            {
                g.SendMessage(messageToNotify, received, SendMessageOptions.DontRequireReceiver);
            }

            // Clear message
            received = "";
        }
    }

    // Call this method to send a message from this app to ipSend using portSend
    public void SendValue (string valueToSend)
    {
        try 
        {
            if (valueToSend != "") 
            {
                // Get bytes from string
                byte[] data = Encoding.UTF8.GetBytes (valueToSend);

                // Send bytes to remote client
                client.Send (data, data.Length, remoteEndPoint);
                Debug.Log ("UDPClient: send \'" + valueToSend + "\'");

                // Clear message
                valueToSend = "";
            }
        } 
        catch (Exception err) 
        {
            Debug.LogError ("Error udp send : " + err.Message);
        }
    }

    // This method checks if the app receives any message
    public void ReceiveData ()
    {
        while (true) 
        {
            try 
            {
                // Bytes received
                IPEndPoint anyIP = new IPEndPoint (IPAddress.Any, 0);
                byte[] data = client.Receive (ref anyIP);

                // Bytes into text
                string text = "";
                text = Encoding.UTF8.GetString (data);
    
                received = text;
                Debug.Log("UDPClient: message received \'" + received + "\'");
        
            } 
            catch (Exception err) 
            {
                Debug.Log ("Error:" + err.ToString ());
            }
        }
    }
        
    // Exit UDP client
    public void OnDisable ()
    {
        if (receiveThread != null) 
        {
            receiveThread.Abort ();
            receiveThread = null;
        }

        client.Close ();
        Debug.Log ("UDPClient: exit");
    }
}

排查分析

  1. 线程安全问题:received变量在子线程(ReceiveData)和主线程(Update)间直接读写,无同步机制,易导致数据覆盖或丢失,比如主线程还没处理完上一条数据,子线程就覆盖了received的值。
  2. 数据流转断层:Update仅将接收到的字符串通过SendMessage传递给其他对象,但未调用SetData将解析后的Agente数据更新到RealTimeItem列表,导致接收的数据无法作用到Unity物体上。
  3. 物体更新逻辑被禁用:FixedUpdate中负责更新物体位置和朝向的代码被注释,即使RealTimeItem的Position和Target被正确设置,物体也不会有任何变化。
  4. GetID方法逻辑错误:找不到对应NetID的项时直接返回0,会错误修改列表中第一个RealTimeItem的数据,而非跳过或报错。
  5. UDP数据完整性风险:虽然Debug显示数据完整,但Anylogic添加库后可能改变了数据包发送方式(如拆包/粘包),仅通过单次Receive无法保证拿到完整的有效数据,可能导致解析失败。

解决方案

1. 修复线程安全问题

使用ConcurrentQueue<string>替代单个string变量存储接收的消息,避免多线程竞态条件:

// 替换原private string received = "";
private ConcurrentQueue<string> receivedMessages = new ConcurrentQueue<string>();

// ReceiveData中修改为入队
public void ReceiveData ()
{
    while (true) 
    {
        try 
        {
            IPEndPoint anyIP = new IPEndPoint (IPAddress.Any, 0);
            byte[] data = client.Receive (ref anyIP);
            string text = Encoding.UTF8.GetString (data);
            receivedMessages.Enqueue(text); // 入队
            Debug.Log("UDPClient: message received \'" + text + "\'");
        } 
        catch (Exception err) 
        {
            Debug.Log ("Error:" + err.ToString ());
        }
    }
}

// Update中修改为循环出队处理
void Update ()
{
    while(receivedMessages.TryDequeue(out string received))
    {
        // 保留原SendMessage逻辑
        foreach (GameObject g in notifyObjects)
        {
            g.SendMessage(messageToNotify, received, SendMessageOptions.DontRequireReceiver);
        }

        // 新增解析与更新逻辑
        Agente ag = JSONReader.ParseAgente(received); // 假设JSONReader有解析方法
        if(ag != null)
        {
            SetData(ag);
        }
    }
}

2. 启用物体更新逻辑

取消FixedUpdate中代码的注释,让物体根据RealTimeItem的数据更新:

private void FixedUpdate()
{
    foreach(var xItem in items)
    {
        xItem.Object.transform.position = Vector3.MoveTowards(xItem.Object.transform.position, xItem.Position, TimeStamp * Time.deltaTime);
        xItem.Object.transform.LookAt(xItem.Target);
    }
}

3. 修复GetID方法逻辑

找不到对应ID时返回-1,并在SetData中做判断,避免错误修改数据:

public int GetID(int SearchID)
{
    for(int i=0; i<items.Count; i++)
    {
        if (items[i].NetID.Equals(SearchID)) 
            return i;
    }
    return -1; // 找不到返回-1
}

public bool SetData(Agente ag)
{
    int itemIndex = GetID(ag.id);
    if (itemIndex == -1) 
        return false;

    RealTimeItem itemUse = items[itemIndex];
    GameObject obj = itemUse.Object;

    itemUse.Position = ag.coordenadas[ag.coordenadas.Count - 1];
    itemUse.Target = ag.coordenadas[ag.coordenadas.Count - 1];
    itemUse.TimeReach = ag.tiempos[ag.tiempos.Count - 1];
    itemUse.TimeReachNext = ag.tiempos[ag.tiempos.Count - 1];
    itemUse.change = true;

    return true;
}

4. 增加UDP数据完整性校验

在Anylogic发送数据时,每条数据末尾添加固定分隔符(如\n),Unity端接收时拼接数据直到遇到分隔符,确保拿到完整的有效数据:

// 修改ReceiveData中的数据处理逻辑
private StringBuilder receivedBuffer = new StringBuilder();
public void ReceiveData ()
{
    while (true) 
    {
        try 
        {
            IPEndPoint anyIP = new IPEndPoint (IPAddress.Any, 0);
            byte[] data = client.Receive (ref anyIP);
            string text = Encoding.UTF8.GetString (data);
            receivedBuffer.Append(text);

            // 按分隔符拆分完整消息
            while(receivedBuffer.ToString().Contains("\n"))
            {
                int splitIndex = receivedBuffer.ToString().IndexOf("\n");
                string fullMessage = receivedBuffer.ToString().Substring(0, splitIndex);
                receivedBuffer.Remove(0, splitIndex + 1);
                receivedMessages.Enqueue(fullMessage);
                Debug.Log("UDPClient: full message received \'" + fullMessage + "\'");
            }
        } 
        catch (Exception err) 
        {
            Debug.Log ("Error:" + err.ToString ());
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 12:35:28