当对来自外部进程的事件调用方法时,Unity 变量消失
Unity variables disappear when a method is called on an event from an external process
我正在使用 Unity 和外部引擎(用 c# 编写的可执行文件)编写国际象棋 UI。我可以向流程(引擎)发送数据和从流程(引擎)接收数据。这是可行的,但是当调用 Make_Move 方法时,当进程用数据响应时,就会出现问题。在调试代码时,当尝试访问 Make_Move 方法中的 Unity 对象时,代码执行会停止,并且缺少对象,即 gameObject 和 sprite,但其余变量(不是统一对象)仍然存在.我没有收到任何错误,所有变量都是 class 的一部分,它保存在一个数组中,该数组跟踪它的精灵、游戏对象以及其他东西。
为什么只有 unity 对象会从对象数组中消失?
为什么在尝试访问统一对象(精灵等)时代码执行停止?
如何解决这个问题?
Unity class 用于发送和接收 4 位字符串,移动的开始和结束位置 (xyxy)
public static class UCI
{
static Process process = new Process();
static UCI()
{
ProcessStartInfo si = new ProcessStartInfo()
{
FileName = "ChessEngine.exe",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true
};
process.StartInfo = si;
process.OutputDataReceived += new DataReceivedEventHandler(OnRecieved);
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
}
public static void SendLine(string command)
{
Debug.Log("send: "+ command);
process.StandardInput.WriteLine(command);
process.StandardInput.Flush();
}
public static void Kill()
{
process.Kill();
}
private static void OnRecieved(object sender, DataReceivedEventArgs e)
{
string text = e.Data;
Debug.Log("Recieved: " + text);
if(text.Length == 4)
Game.Make_Move(new Move(Board.Squares[(int)char.GetNumericValue(text[0]),(int)char.GetNumericValue(text[1])],Board.Squares[(int)char.GetNumericValue(text[2]), (int)char.GetNumericValue(text[3])]));
}
}
Unity make_Move方法出现问题的地方:
static void Make_Move(Move move)
{
var end = move.end.GetPiece();
//posistion is a vector2Int, this line does not cause any trouble
print("Pos: " + Squares[move.start.GetPosition().x, move.start.GetPosition().y].GetPosition());
//this line causes execution to stop without error. if it is commented out execution will continue.
print("obj: " + Squares[move.start.GetPosition().x, move.start.GetPosition().y].gameObject);
//lines below this is not executed
SetPiece(move.start.gameObject.GetComponent<Image>().sprite, move.end.GetPosition(), end.MoveCnt);
RemovePiece(move.start.GetPosition());
// set the current turn to the other player
CurrentPlayer = CurrentPlayer == players[0] ? CurrentPlayer = players[1] : CurrentPlayer = players[0];
}
main scene chess board image
(大部分)Unity API不能被后台线程使用!
我不知道你的类型 using/storing 在 Squares
数组中 但是 :请注意,大多数 Unity API 直接影响或依赖于场景(例如任何变换 属性、.gameObject
等)仅适用于 Unity 主线程! (例外是例如简单的 Vector3
操作,只要您不将它们分配给 Transform
)
我很确定这里的问题是你的 OnReceived
是在不同的线程(启动进程之一)上执行的 => 你甚至没有在主线程中看到异常!
Unity 中经常使用的一种模式是所谓的“主线程调度程序”,可能类似于
public class MainThreadDispatcher : MonoBehaviour
{
// Here we store the current existing instance of this component
private static MainThreadDispatcher _instance;
// Public read-only access
public static MainThreadDispatcher Instance => _instance;
// A thread-safe Queue (first-in/first-out) for the actions
// that shall be executed in the next Update call
private readonly ConcurrentQueue<Action> _actions = new ConcurrentQueue<Action>();
private void Awake ()
{
// Make sure only one instance of this exists (Singleton Pattern)
if(_instance && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
// Optional but recommended: Make sure this object is not
// destroyed if the scene is changed
DontDestroyOnLoad (gameObject);
}
private void Update ()
{
// Run all actions that have been enqueued since the last frame
while(_actions.TryDequeue(out var action)
{
action?.Invoke();
}
}
// Call this from any thread to make an action be executed in the next Update call
public void DoInMainThread(Action action)
{
// Simply add the action to the end of the thread-safe Queue
_actions.Enqueue(action);
}
}
然后你会class做
private static void OnRecieved(object sender, DataReceivedEventArgs e)
{
var text = e.Data;
Debug.Log("Recieved: " + text);
if(text.Length == 4)
{
MainThreadDispatcher.Instance.DoInMainThread(() =>
{
// Delay this code line until we are in the next unity main thread
// "Update" call where Unity API can be safely used
Game.MakeMove(new Move(Board.Squares[(int)char.GetNumericValue(text[0]),(int)char.GetNumericValue(text[1])],Board.Squares[(int)char.GetNumericValue(text[2]), (int)char.GetNumericValue(text[3])]));
});
}
}
现在您所要做的就是确保将 MainThreadDispatcher
组件附加到第一个场景中任何始终处于活动状态的游戏对象。