Making Coroutine called twice run sequentially or alternative

I have a coroutine for running through an array of dialog text. It works fine. But now I have a quest system that checks if you may have completed the conditions for it already. So if the quest/task has dialog attached to it or dialog != null… Then it runs the dialog on completion of the quest/task. The for me arises when I have two quests/tasks that are completed. When the first one is completed it calls the coroutine for dialog which being asynchronous causes the for loop that is looping through the tasks to continue and so then both quests end up being completed at about the same time and the dialog for both is run at the same time so it jumbles up the dialog on screen and you only get one series of dialog instead two.

Is there a way I can block execution of one until the other is done?

I think if I could do a return type I could have it return true and wait for that boolean to be passed but since the Dialog coroutine is an IENumerator I can’t return a boolean.

I have found some things on stackoverflow that hint that task return type and async/await might be a possible solution but all the examples are for handling web traffic/server calls and stuff not the actual the local game state, frankly not very familiar with Task or async outside of javascript so in terms of using it in C# and Unity I don’t know where to start.

When it’s two coroutines in succession, in the same code block, I have done “yield return” to make them work sequentially.

But this is a single coroutine in a loop and it’s the same coroutine being called twice.

it looks like this:

{
if (condition)
{
StartCoroutine(Dialog());
}
}```

Is there a way I can make this coroutine wait for the first call before running the other? If not, could you point me in a direction towards would could possibly handle this situation?

Does this need to be a coroutine? That’s the first question I’d ask. Perhaps use coroutines for the actual spilling out of a given “unit” of dialog or quest, but the quest itself should just be a series of steps and then an integer counter that steps through them.

As far as blocking when some coroutine is happening, the easiest way is to set a boolean when you start it, and have it clear the boolean at the end, something like bool dialogInProgress; for instance.

You may need a little state machine for the quest presentation too, to track what it is doing:

  • nothing
  • giving you a quest
  • waiting for you to complete a quest
  • giving you the reward
  • chaining onto the next quest

etc

1 Like

If you want to use coroutines, probably what you want is a queue of coroutines, and then a coroutine that goes through that queue yielding each one so that they run one at a time.

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ConsecutiveCoroutines : MonoBehaviour
{
    private Queue<IEnumerator> coroutineQueue = new();
    public bool coroutinesRunning = false;
    // Start is called before the first frame update
    void Start()
    {
        coroutineQueue.Enqueue(Dialogue("Good Morning"));
        coroutineQueue.Enqueue(Dialogue("Good Night"));
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space) && !coroutinesRunning)
        {
            StartCoroutine(Consecutive(coroutineQueue, (isRunning) => coroutinesRunning = isRunning));
        }
    }

    private IEnumerator Dialogue(string dialogue)
    {
        foreach (char ch in dialogue)
        {
            Debug.Log(ch);
            yield return new WaitForSeconds(0.2f);
        }
      
    }

    private IEnumerator Consecutive(Queue<IEnumerator> coroutines, Action<bool> SetIsRunning)
    {
        SetIsRunning(true);

        while(coroutines.TryDequeue(out IEnumerator coroutine))
        {
            yield return StartCoroutine(coroutine);
            yield return new WaitForSeconds(1);
        }

        SetIsRunning(false);
    }
}

I also included the passing in of an anonymous function to the Consecutive coroutine that sets its external flag variable, just to show you how to get functionality similar to the return you were talking about.

Seeing as though the coroutine and the boolean are in the same scope in this case, you could just refer to it directly in the coroutine instead. The setter is really for when a variable is out of scope of the coroutine.

2 Likes

The queue is exactly what I need. It was something I was trying to think about myself and I couldn’t figure it out as I’ve used something similar to queuing up cutscenes or segments of cutscenes. Thanks a lot, both of you guys presented great solutions. For my situation the quest system is rather complex with many loops and tasks/milestones within a single quest so the queue is definitely what I need it’s just too hard to figure out where things go wrong if I try to get it to work in the loops. It’s probably because I’ve coupled too many things as I tend to like my singleton’s way more than I probably should. Playing them sequentially at the end from the queue is probably the most straightforward.

Thanks guys

1 Like

Note that Unity now supports running properly nested coroutines. So this line:

yield return StartCoroutine(coroutine);

may be replaced by

yield return coroutine;

When you now yield an IEnumerator, the Unity coroutine scheduler will run that IEnumerator object as nested iterator and continues the outer one once the inner one has finished. This avoids all sorts of strange and often unwanted effects when one or more coroutines are stopped. Stopping the outer coroutine would also stop all nested coroutines as they run essentially inside the same coroutine. When you use StartCoroutine you actually start a new completely seperate coroutine and the outer one is simply waiting for the completion. So stopping the outer coroutine would not stop the nested one in that case. Also the overall overhead should be less. Internally each coroutine probably has a Stack of IEnumerators and it simply iterates the top element. When it completes the nested element is popped from the stack and it continues with the outer one until the stack is empty which means the whole coroutine is done.

2 Likes