我是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编辑器)。这导致以下控制台屏幕截图:
这证明主Update()循环正在运行,而我希望它只在从服务器收到“pong”时才暂停和更新。任何想法如何实现这一目标?
我搜索了论坛的答案,但总是偶然发现了相反的问题 - 如何让Unity不要冻结 - 以及建议使用Coroutines或Threads的答案。
非常感谢任何帮助。
提前致谢!!!
答案 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的内部绘图机制,直到您从服务器收到新数据?
如果是这种情况,那么只需在Update
或LateUpdate
方法中等待它:
void Update() // or void LateUpdate()
{
while(!received && Application.isRunning){
// do nothing
}
// receive
string msg = readSocket();
// log
Debug.Log(msg);
}
但这不是你应该如何使用Unity
引擎。
如果那不是您想要的东西,那么就发表评论,我会删除这个答案:)