【问题标题】:Is there a way to stay in a coroutine until a condition is met?有没有办法在满足条件之前留在协程中?
【发布时间】:2026-02-01 02:50:01
【问题描述】:

我正在尝试编写一个非常简单的对话系统。当玩家获得一个对话框时,该对话框会一直存在,直到他们按下一个键。然后出现第二个对话框。再次,他们按下一个键,依此类推。

然而,我意识到我使用协程的方法不起作用,因为每个对话框都会同时显示。使用简化版本:

IEnumerator Test() {
        //Code that displays a message
        yield return StartCoroutine(WaitForKey(KeyCode.Space));
    }

    IEnumerator WaitForKey(KeyCode keyCode)
    {
        while (!Input.GetKeyDown(keyCode))
            yield return null;
    }

    void start(){
        StartCoroutine(Test());
        StartCoroutine(Test());
    }

上面代码的结果是显示了两条消息。另外,它们会立即显示——一旦满足条件,进程就会从那个coutine跳转到启动,运行下一行代码,并定期返回第一个协程以查看它是否已完成。

如何让一个协程在它继续执行其后的其余代码之前完成?

【问题讨论】:

  • 你有想要在数组中显示的对话框吗?您如何按顺序设置对话框?这对我很有帮助。
  • @programmer:我没有任何数组,因为它是一种逐个基本函数。目前,每当我想弹出消息时,我都会使用:StartCoroutine(dh.showMessage ("NameOfSpeaker", "Stuff I am saying"));
  • 就是这样;我事先不知道正文。我还不熟悉如何创建画布。我非常感谢您提供的所有帮助,尤其是您提供的所有代码,但我响应缓慢,因为我仍在尝试解析它。
  • 除了创建一组画布之外,我什么都知道。这也不会使从文件中读取文本难以进行对话吗?

标签: c# unity3d


【解决方案1】:

解决这个问题的简单方法是创建 Canvas 数组作为对话框。在编辑器中禁用所有这些。使用下面的代码,您可以更改 dialogueGameObjectCanvas 的数组size 为您有多少对话,然后按顺序分配每个对话的父级到编辑器中的 dialogueGameObjectCanvas 数组。 Cancas 必须是每个对话的父级。运行。

按空格键将进入下一个对话。这是最简单的方法。

现在,以专业的方式。如果对话是在运行时生成,您可以使用List添加remove 对话框(GameObjectUICanvas)而不是使用数组来完成。您可以轻松地将数组转换为列表,这应该适用于动态生成的对话。

startDisplayDialogue() 开始对话。

isDialogueDisplaying() 告诉您对话当前是否显示在屏幕上。

getDialogueCurrentNumberDisplaying() 获取当前显示的对话数组数

stopDisplayDialogue() 杀死整个对话并关闭它们。

记住startDisplayDialogue()是协程函数,必须以StartCoroutine (startDisplayDialogue ());开头

public GameObject[]dialogueGameObjectCanvas;
bool isRunning = false;
int dialogNumberDisplaying = 0;

// Use this for initialization
void Start ()
{
    //animationSprite = new GameObject[5]; Uncomment if you want to assign through code, otherwise assign the dialogues from the Editor
    StartCoroutine (startDisplayDialogue ());
}

//Display the first dialogue then wait for the space to be pressed then display the next dialue
IEnumerator startDisplayDialogue ()
{
    //Make sure there is only one instance of the dialuge functionr running. Break if there is alread one running 
    if (isRunning) {
        yield break;
    } else {
        isRunning = true;
    }

    int dialogueNumber = 0; //Starts from 0 to the size of the animationSprite array
    dialogNumberDisplaying = dialogueNumber;

    while (isRunning) {
        //Stop if the dialogNumber is >= number of dialogues to dispaly(dialogue arrays)
        if (dialogueNumber >= dialogueGameObjectCanvas.Length) {
            isRunning = false;
            //dialogueGameObjectCanvas [dialogueNumber].SetActive (false);
            Debug.Log ("End of dialogue");
            yield break;
        }

        //Dispaly the current Dialogue based on the array number
        dialogueGameObjectCanvas [dialogueNumber].SetActive (true);

        //Wait until the Space bar is pressed 
        while (!Input.GetKeyDown(KeyCode.Space)) {
            yield return null; //Must wait here or else Unity would freeze
        }

        //Space bar has been pressed. Disable the current Dialogue then increement dialogueNumber so that then the next one in the array will display
        dialogueGameObjectCanvas [dialogueNumber].SetActive (false);
        dialogueNumber++;
        dialogNumberDisplaying = dialogueNumber; //For the getDialogueCurrentNumberDisplaying function


        //Check if stopDisplayDialogue is called then exit
        if (!isRunning) {
            dialogueGameObjectCanvas [dialogueNumber].SetActive (false);
            yield break;
        }
        yield return null;
    }
}

//Check if a dialogue is currently displaying
bool isDialogueDisplaying ()
{
    return isRunning;
}

//Get the current array number of dialogue currently displaying
int getDialogueCurrentNumberDisplaying ()
{
    return dialogNumberDisplaying;
}

//Stops the dialogue from displaying
void stopDisplayDialogue ()
{
    isRunning = false;
}

【讨论】:

    【解决方案2】:

    您的问题是您在同一帧中调用StartCoroutine(Test()) 两次,仅当您使用 yield return StartCouroutine(Test()); 下一行是否会等到协程完成执行。

    所以现在你的 start 函数将运行 Test() 函数,直到它首先得到它 yield return X,然后它会回到 Start 函数的第二行并做同样的事情。然后在下一帧(或当您的 yield return X 完成时)它将继续执行这些例程。

    如果我了解您在做什么,您可以在您的测试功能中使用while(true) 来解决您的问题。但是,如果您发布更详细的代码,我可以为您提供更好的建议,说明为什么您的方法没有按您期望的方式工作!

    【讨论】:

    • 如果我理解正确的话,如果我使用while(true),要么两个函数仍然会同时播放,要么都不播放。也就是说,如果我使用while(!Input.GetKeyDown( Keycode.Space)),那么当第一个 Test() 启动循环时,协程会将其踢出并启动第二个 Test(),这将启动第二个循环。然后这两个函数将再次同时播放,而不是第一个 Test() 播放到完成,然后是第二个 Test();播放直到完成。
    • 是的,防止两者同时运行的唯一方法是启动另一个协程,该协程在该协程中执行两次 yield return StartCoroutine(Test())。您还可以选择将 Start() 设为 'IEnumerator Start()' 而不是 'void Start()',这将使 start 自动成为协程,允许您在 Start 内执行 'yield return StartCoroutine(Test())' .
    • 如果您发现另一个框立即被关闭,您可能还想在 WaitForKey 函数末尾执行Input.ResetInputAxes();
    • 你能给我一个yield return StartCoroutine(Test() 解决方案的例子吗?我不确定我是否关注
    • 基本上在任何协程中,您都可以 yield return 启动另一个协程,这将等待您刚刚开始的协程完成,然后再执行下一行。