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

时间:2017-03-03 08:31:07

标签: c# python unity3d tcp

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

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

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

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

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

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

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

  • 冻结框架
  • 计算
  • 乒乓
  • 下一帧

所以在Unity Start()方法中,我设置套接字并发送初始ping。我希望在主Update()循环中让Unity在更新帧之前等待python的pong答案。我创建了一个包含玩具示例的脚本,并尝试通过在将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编辑器)。这导致以下控制台屏幕截图:

enter image description here

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

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

非常感谢任何帮助。

提前致谢!!!

4 个答案:

答案 0 :(得分:2)

  

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

没错。在您的公告发布之后,这也是我最初的想法:

  • 冻结框架
  • 计算
  • 乒乓
  • 下一帧

转换成代码将是:

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。这可能是你想要的东西,但它在任何方面都不好。

您必须在主题中执行此操作。原因是您正在使用接收函数的阻止版本。

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

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

请勿使用主线程中的套接字读取,就像您目前正在做的那样。一个例外是使用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。

答案 1 :(得分:1)

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

答案 2 :(得分:1)

您可以使用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;
        }
    }
}

答案 3 :(得分:0)

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

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

void Update() // or void LateUpdate()
{
    while(!received && Application.isRunning){
        // do nothing
    }

    // receive
    string msg = readSocket();
    // log
    Debug.Log(msg);
}

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

如果那不是您想要的东西,那么就发表评论,我会删除这个答案:)