【问题标题】:Wait for Response before continuing Main Routine在继续主程序之前等待响应
【发布时间】:2020-01-18 23:24:47
【问题描述】:

我正在尝试制作一个简单的消息框,以帮助指导我的输出的可读性。我在 Unity 中使用 C#。

原因是消息通常很长、令人费解或偶尔需要回复。

我正在尝试使用简单的bool 标志调用DisplayMessage(string _message) 方法,以使其关闭并继续。但无论我做什么,它要么冻结(谢谢while 无限循环)要么传递到主线程

IEnumerables 不是我的强项之一,除非我遗漏了一些基本和明显的东西(我非常愿意承认这种可能性),否则协程仍然无法正确支持 DoDisplay(),直到我获取按钮输入

我花了几个小时寻找,但我的眼睛对 Systems.Threading 解决方案的选项感到茫然

public Class MainTurn: MonoBehavior {
    public GameControl gCon;

    public void OnPhaseOne(GameControl gCon) {
        // *** EVENT CARDS ***

        DisplayManager dMan = GameObject.FindGameObjectWithTag("Display Manager").GetComponent<DisplayManager>();
        dMan.DoDisplay("Take 2 Cards, update Red Count");

        // Check for 6+ Event Cards
        if (gCon.Cards >= 6) {
            dMan.DoDisplay("Discard a Card\n Discard Order:\n1. 2. 3. 4.");
        }

        // Allow for Exchange
        if (gCon.Cards == 2) {
            dMan.DoDisplay("Exchange Card as Required");
        }

    }
}

    public class DisplayManager : MonoBehaviour {
        public GameObject DisplayPanel;

        public Text Message_Text;
        public Button Complete_Button;

        private bool _isComplete;

        public void DoDisplay(string _displayMessage) 
        {
            // Activate Panel
            DisplayPanel.SetActive(true);

            // Set flag and display message
            _isComplete = false;
            Message_Text.text = _displayMessage;

            // Wait until Complete
            while (_isComplete == false) 
            {
                Debug.Log("Waiting");
            }

            //Clear and close
            Message_Text.text = "";
            DisplayPanel.SetActive(false);
        }

        // A simple Button press to clear the flag
        public void OnComplete() 
        {
            _isComplete = true;
        }
    }

【问题讨论】:

    标签: c# unity3d


    【解决方案1】:

    首先:您确实不想在这里使用线程!将它们用于文件 IO 和序列化等繁重的操作,是的,等等,但不仅仅是等到您的应用程序发生某些事情。唯一能实现的就是必须额外注意将相应的回调传递回主线程等。

    不需要例程/循环

    如果它只是关于不需要任何持续更新或平滑移动等的一次性操作,那么如何使其基于事件而不使用bool 标志和例程或循环根本:

    public void DoDisplay(string _displayMessage) 
    {
        // Activate Panel
        DisplayPanel.SetActive(true);
    
        // display message
        Message_Text.text = _displayMessage;
    
        // log only once
        Debug.Log("Waiting");
    }
    
    // A simple Button press to clear the flag
    public void OnComplete() 
    {
        //Clear and close
        Message_Text.text = "";
        DisplayPanel.SetActive(false);
    }
    

    一般来说,您应该避免在框架上使用Debug.Log,因为即使您在构建中看不到控制台输出,它也会减慢您的应用程序。


    协程

    既然你问了 - 替代方案可以使用 Coroutine 和 WaitWhile 或 WaitUntil,或者如果你真的想要再次使用简单的 yield return null 循环 while。

    请注意,协程仍在主线程中执行,因此此处的任何繁重操作仍可能会冻结您的应用程序 - 例如,如果您在其中的任何位置都没有 yield return,则关闭 while 循环;)

    将它们视为临时的Update 方法,因为实际上(使用默认的yield returns)它是在实际的Update 方法之后逐帧执行的。 也有例外,例如WaitForFixedUpdate 或 WaitForEndOfFrame 但现在可能会走得很远。

    对于 Unity 的协程 yield return 来说,简单来说就是“暂停这个例程,继续执行主线程。稍后从你离开的地方继续这个例程。”

    public void DoDisplay(string _displayMessage)
    {
        // Be careful now with concurrent routines in case this gets called twice
        // You probably would want to stop the last routine and start a new one
        StopAllCoroutines();
        StartCoroutine(DoDisplayRoutine(_displayMessage));
    }
    
    public IEnumerator DoDisplayRoutine(string _displayMessage) 
    {
        // Activate Panel
        DisplayPanel.SetActive(true);
    
        // Set flag and display message
        _isComplete = false;
        Message_Text.text = _displayMessage;
    
        Debug.Log("Waiting");
        yield return new WaitUntil(() => _isComplete);
    
        // OR if you really want to display the log every frame meanwhile
        while(!_isComplete)
        {
            Debug.Log("Waiting");
            // render this frame and continue from here in the next one
            // If you missed to yield return somewhere in a while loop
            // then yes, this would also freeze the main thread
            yield return null;
        }
    
        //Clear and close
        Message_Text.text = "";
        DisplayPanel.SetActive(false);
    }
    
    // A simple Button press to clear the flag
    public void OnComplete() 
    {
        _isComplete = true;
    }
    

    我不知道你的主代码做了什么,因为你没有添加它......你可以做的就是让它成为一个例程,就像例如

    IEnumerator YourMainCode()
    {
        ... do something
    
        // show message and wait until it gets closed (using the later coroutine solution)
        yield return DoDisplayRoutine(_displayMessage);
    
        ... continue after message was closed
    }
    

    现在我们有了你的代码;)

    如你所说,你可以这样做

    public void OnPhaseOne(GameControl gCon) 
    {
        StartCoroutine(PhaseOneRoutine());
    }
    
    private IEnumerator PhaseOneRoutine()
        // *** EVENT CARDS ***
    
        // if there is only one in the sene you can simply use
        DisplayManager dMan = FindObjectOfType<DisplayManager>();
        // in general you should rather do it in Awake and only once
    
        // this now "blocks" until the message is completed
        yield return dMan.DoDisplayRoutine("Take 2 Cards, update Red Count");
    
        // Check for 6+ Event Cards
        if (gCon.Cards >= 6) {
            yield return dMan.DoDisplayRoutine("Discard a Card\n Discard Order:\n1. 2. 3. 4.");
        }
    
        // Allow for Exchange
        if (gCon.Cards == 2) {
            yield return dMan.DoDisplayRoutine("Exchange Card as Required");
        }
    }
    

    【讨论】:

    • 谢谢你,derHugo。不幸的是,这不起作用,它仍然直接传递到主程序的其余部分,而无需等待按下按钮。很清楚 Debug.Log 的成本,但无论如何感谢。我主要使用它们来跟踪正在发生的事情和发生的地点。这个项目不需要任何管理费用,所以它(目前)是我最不关心的。您的代码与我最初尝试的版本相似(尽管更优雅!)
    • @Wibble 这是不可能的......要么OnComplete 必须被调用,要么_isComplete 设置为true .. 你确定你是从别处做的吗?或者等等..it is still passing right through to the rest of the main routine 是什么意思...有没有你没有展示的例程?
    • 代码的流程是:主分支代码(变量条件集)经过5个阶段。在每个阶段的多个点上,它调用 DoDisplay() 以显示消息或获取一些输入,然后返回到主代码当主代码调用 DoDisplay() 时,我需要它成为一个障碍,因为它是, 在返回主代码之前。
    • 更新了我原来的例子
    • 如果不知道你是如何调用你的方法的,真的很难提供帮助......我添加了一个例子,我只能猜测你正在尝试什么
    【解决方案2】:

    情况

    据我所知,您需要以下内容:

    • 当没有其他消息显示时在屏幕上显示消息
    • 如果显示另一条消息,请在显示下一条之前等待

    这不需要复杂的元素,例如线程、协程或其他任务管理器。这是Queue 的工作。

    你可以实现这个算法:

    算法

    1. 尝试显示消息
    2. NO PREVIOUS MESSAGE => 确认显示消息
    3. 屏幕上的先前消息 => 将消息排入队列
    4. 调用完成时
    5. 队列没有消息 => 关闭菜单
    6. QUEUE HAS MESSAGES => 显示下一条消息

    消息队列

    使用您的代码的实现如下。

    public class DisplayManager : MonoBehaviour
    {
        public GameObject DisplayPanel;
    
        public Text Message_Text;
        public Button Complete_Button;
    
        private Queue<string> _messages;
    
        public void DoDisplay(string displayMessage)
        {
            //if no messages are displayed, you display the message 
            if (string.IsNullOrEmpty(Message_Text.text)) ShowMessage(displayMessage);
            //if other messages are displayed you enqueue the message
            else _messages.Enqueue(displayMessage);
        }
    
        private void ShowMessage(string displayMessage)
        {
            // Activate Panel, if not already active
            if(!DisplayPanel.activeSelf) DisplayPanel.SetActive(true);
    
            // set the message on the text
            Message_Text.text = displayMessage;
        }
    
        // A simple Button press to clear the flag
        public void OnComplete()
        {
            //if we have other messages we show the next
            if (_messages.Count > 0) ShowMessage(_messages.Dequeue());
            //otherwise we close the menu
            else CloseMessage();
        }
    
        private void CloseMessage()
        {
            Message_Text.text = "";
            DisplayPanel.SetActive(false);
        }
    }
    

    进一步的逻辑

    如果您想在消息显示器上添加更多逻辑,您可以在 ShowMessage 方法上执行此操作。

    在那里你可以添加任何东西,比如

    if(displayMessage == "Add Card") 
        //DO SOMETHING
    

    【讨论】:

    • 谢谢@Jack Mariani。不幸的是,队列不会成为这里的解决方案,因为消息会在显示消息的面板上提示(次要)活动。这些更改以及偶尔来自消息的响应将直接影响主代码中的后续操作。如果我只是为了显示队列,那么后面的结果就会出现偏差
    • 请记住,您可以在ShowMessage 中添加任何进一步的逻辑。或者你也可以实现一个OnMessage 事件。还要记住.Net 中有Action,您可以实现Action 或什至`Action` 的队列,而不是消息队列。无论如何,队列字符串解决了您发送的示例,如果您添加更多数据,可能会有更多选项。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-01-17
    • 2014-07-12
    • 2016-04-02
    • 2012-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多