【问题标题】:Unity variables disappear when a method is called on an event from an external process当从外部进程的事件调用方法时,Unity 变量会消失
【发布时间】:2021-02-21 20:33:03
【问题描述】:

我正在使用 Unity 和外部引擎(用 c# 编写的可执行文件)编写国际象棋 UI。我可以向流程(引擎)发送和接收数据。这是可行的,但是当调用 Make_Move 方法时,当进程返回数据时,就会出现问题。虽然调试代码执行只是在尝试访问 Make_Move 方法中的 Unity 对象时停止,并且缺少对象,即部分 gameObject 和 sprite,但其余变量(不是统一对象)仍然存在。我没有收到任何错误,所有变量都是一个类的一部分,该类保存在一个数组中,该数组跟踪它的精灵游戏对象以及其他东西。

为什么只有统一对象会从对象数组中消失?

为什么在尝试访问统一对象(精灵等)时代码执行停止?

如何解决这个问题?

用于发送和接收 4 位字符串的 Unity 类,移动的开始和结束位置 (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

【问题讨论】:

    标签: c# unity3d events chess


    【解决方案1】:

    (大部分)Unity API 不能被后台线程使用!

    我不知道您在 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);
        }
    }
    

    那么你会在你的课堂上做

    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 组件附加到任何始终处于活动状态的游戏对象。

    【讨论】:

    • 我认为你是对的谢谢我会试一试,我只有一个场景(2d 棋盘)所以 MainThreadDispatcher 应该附加到 Square 数组中的所有游戏对象?难道没有更简单/正常的方式来统一与外部进程进行通信吗?
    • @HannoKruger 在您的第一个场景中,它只能完全附加到 ONE 单个对象;)我会建议例如一个新的名为“MainThreadDispatcher”的空游戏对象,只附加了这个组件
    猜你喜欢
    • 1970-01-01
    • 2011-08-07
    • 1970-01-01
    • 1970-01-01
    • 2018-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多