当对来自外部进程的事件调用方法时,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 组件附加到第一个场景中任何始终处于活动状态的游戏对象。