首页 文章

Unity作为模拟环境 - 在执行大计算时延迟Update()循环

提问于
浏览
1

我是Unity的新手,我目前正在将其用作为我的试点研究设计简单模拟环境的工具 . 这是一个简化的解释:

主角(代理)扫描环境并通过TCP套接字将状态(作为JSON对象)发送到python服务器 .

在python服务器端,根据状态执行大的计算 . 结果是通过TCP发送回Unity的操作 .

角色需要在Unity中执行此操作,然后再次扫描环境 . 新状态再次发送到python服务器....

这个过程正在重复“直到无限(除非客户端或服务器停止),最终导致代理根据其学习自行开发行为 .

我完成了团结环境的创建,并在python中编写了学习算法 . 而且,我设法在两者之间 Build TCP连接 . 但是,我偶然发现了Unity中主要的Update()循环问题 .

也就是说,如果我简化Unity发送ping(作为状态)和python发送pong(作为动作)的过程,我需要重复以下过程 .

  • 冻结帧

  • ping

  • 计算

  • pong

  • 下一帧

所以在Unity Start() 方法中我设置套接字并发送初始ping . 我想在主 Update() 循环中让Unity在更新帧之前等待python的乒乓答案 . 我创建了一个包含玩具示例的脚本,并尝试通过在将pong发送到Unity之前添加2秒的延迟来模拟python中的计算时间 .

以下是Unity脚本的代码(只需将其附加到主摄像头):

using UnityEngine;
 using System;
 using System.IO;
 using System.Net.Sockets;

 public class networkSocketPingPong : MonoBehaviour
 {
     public String host = "localhost";
     public Int32 port = 50000;

     internal Boolean socket_ready = false;
     internal String input_buffer = "";

     TcpClient tcp_socket;
     NetworkStream net_stream;

     StreamWriter socket_writer;
     StreamReader socket_reader;

     private void Start()
     {
         setupSocket();
         writeSocket("ping");
     }


      void Update()
      {

         string received_data = readSocket();

         switch (received_data)
              {
                  case "pong":
                      Debug.Log("Python controller sent: " + (string)received_data);
                      writeSocket("ping");
                      break;
                  default:
                      Debug.Log("Nothing received from Python");
                      break;
          }
     }

     void OnApplicationQuit()
     {
         closeSocket();
     }

     // Helper methods for:
     //...setting up the communication
     public void setupSocket()
     {
         try
         {
             tcp_socket = new TcpClient(host, port);
             net_stream = tcp_socket.GetStream();
             socket_writer = new StreamWriter(net_stream);
             socket_reader = new StreamReader(net_stream);
             socket_ready = true;
         }
         catch (Exception e)
         {
             // Something went wrong
             Debug.Log("Socket error: " + e);
         }
     }

     //... writing to a socket...
     public void writeSocket(string line)
     {
         if (!socket_ready)
             return;

         socket_writer.Write(line);
         socket_writer.Flush();
     }

     //... reading from a socket...
     public String readSocket()
     {
        if (!socket_ready)
         {
             Debug.Log("Socket is not ready");
             return "";
         }

         if (net_stream.DataAvailable)
             return socket_reader.ReadLine();

         return "";
     }

     //... closing a socket...
     public void closeSocket()
     {
         if (!socket_ready)
             return;

         socket_writer.Close();
         socket_reader.Close();
         tcp_socket.Close();
         socket_ready = false;
     }
 }

...这里是python服务器代码:

import socket
 import time

 host = 'localhost' 
 port = 50000
 backlog = 5 
 size = 1024 
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
 s.bind((host,port)) 
 s.listen(backlog) 

 while 1:
     client, address = s.accept() 
     print "Client connected."
     while 1:
         data = client.recv(size)
         if data == "ping":
             time.sleep(2)
             print ("Unity Sent: " + str(data))
             client.send("pong\n")
         else:
             client.send("Bye!")
             print ("Unity Sent Something Else: " + str(data))
             client.close()
             break

我首先运行python服务器然后运行模拟(通过Unity编辑器) . 这导致以下控制台屏幕截图:

这证明主Update()循环正在运行,而我希望它只在从服务器收到“pong”时才暂停和更新 . 任何想法如何实现这一目标?

我在论坛上搜索了答案,但总是偶然发现了相反的问题 - 如何让Unity不要冻结 - 以及建议使用Coroutines或Threads的答案 .

任何帮助都感激不尽 .

提前致谢!!!

4 回答

  • 2

    您可以使用Coroutine创建Thread:

    public Action<string> OnReceiveMessage;  // event trigger when receive message.
    
    private Thread m_ReceiveThread;
    private List<string> m_ReceiveStrings;
    private int m_ReceiveStrCount;
    
    private void Start() {
        m_ReceiveStrings = new List<string> ();
        m_ReceiveStrCount = m_ReceiveStrings.Count;
        StartCoroutine (CreateReceiveListener());
    }
    
    private IEnumerator CreateReceiveListener() {
        m_ReceiveThread = new Thread (ReceiveListener);
        m_ReceiveThread.IsBackground = true;
        m_ReceiveThread.Start ();
        yield return null;
    }
    

    添加到列表消息:

    private void ReceiveListener() {
        while (m_Connected) {
            string receiveData = ReadSocket ();
            if (string.IsNullOrEmpty (receiveData) == false) {
                m_ReceiveStrings.Add (receiveData);
                Debug.Log (receiveData);
            }
        }
    }
    

    如果有任何变化,请检查lateupdate:

    private void LateUpdate() {
        if (m_ReceiveStrCount != m_ReceiveStrings.Count) {
            if (OnReceiveMessage != null) {
                OnReceiveMessage (m_ReceiveStrings[m_ReceiveStrCount]);
                m_ReceiveStrCount = m_ReceiveStrings.Count;
            }
        }
    }
    
  • 0

    如果我理解正确你想挂起Unity的内部绘图机制,直到你从服务器收到新数据?

    如果是这种情况,那么只需在 UpdateLateUpdate 方法中等待它:

    void Update() // or void LateUpdate()
    {
        while(!received && Application.isRunning){
            // do nothing
        }
    
        // receive
        string msg = readSocket();
        // log
        Debug.Log(msg);
    }
    

    但这不是你应该如何使用 Unity 引擎 .

    如果这不是你想要的东西然后把评论,我会删除这个答案:)

  • 1

    删除Update()函数并将代码添加到WriteSocket()函数的回调函数中 .

  • 1

    我搜索了论坛的答案,但总是偶然发现了相反的问题 - 如何让Unity不要冻结 - 以及建议使用Coroutines或Threads的答案 .

    那就对了 . 在您的公告如下之后,这也是我最初的想法:

    • 冻结帧

    • ping

    • 计算

    • pong

    • 下一帧

    转化为代码将是:

    void Start()
    {
        StartCoroutine(doForever());
    }
    
    
    IEnumerator doForever()
    {
    
        while (true)
        {
            //freeze frame
            Time.timeScale = 0f;
    
            //ping
            writeSocket("ping");
    
            //calculation
    
            //pong
            string pongValue = socket_reader.ReadLine();
    
            //unfeeze frame
            Time.timeScale = 1f;
    
            //next frame
            yield return null;
        }
    }
    

    这在许多方面都是错误的 . 这将在等待或从python接收数据时冻结Unity . 这可能是你想要的东西,但它在任何方面都不好 .

    You must do this in a Thread . 这是因为您使用的是阻止版本的receive函数 .

    Thread socketThread;
    
    void Start()
    {
        socketThread = new Thread(doForever);
        socketThread.IsBackground = true;
        socketThread.Start();
    }
    
    void doForever()
    {
    
        while (true)
        {
            //freeze frame
            gameStatus = GameStatus.PAUSED;
    
            //ping
            writeSocket("ping");
    
            gameStatus = GameStatus.RUNNING;
            //calculation
    
            //pong
            string pongValue = socket_reader.ReadLine();
        }
    }
    

    如果你想从另一个Thread或上面的代码中使用Unity的API,你应该看看这个post .

    Do not use read from socket from the main Thread like you are currently doing . 一个例外是使用 async 函数但在这种情况下不是 .

    这有很多原因,最糟糕的是你的UI没有响应,直到你从python代码收到一些东西 .


    至于暂停你的游戏,你可以简单地使用enum和if语句来完成它 .

    public enum GameStatus
    {
        RUNNING, PAUSED
    }
    

    然后在你发挥作用的每个函数中,你可以做这样的事情:

    GameStatus gameStatus;
    
    void moveCharacterEveryFrame()
    {
        if (gameStatus == GameStatus.PAUSED)
        {
            return;
        }
    
        //Move Character code below
    }
    

    当您想要从上面的那个线程暂停游戏时,您只需执行以下操作:

    gameStatus = GameStatus.PAUSED;
    

    要取消使用:

    gameStatus = GameStatus.RUNNING;
    

    现在,您不会冻结您的游戏或UI .

相关问题