Weird Coroutines behavior

Hi All.

I’ve created a coroutine that displays a message, than waits for a specific key input (KeyCode.E in that case), and displays the next message.

My problem is, that sometimes when pressing E, coroutine skips past more then 1 message.
This is 100% reproducable, so I’ve worked around it by simply putting two calls two coroutine waiting for input instead of one, however, I would like to rootcause this issue and properly fix it.

Here’s part of my code:

    public IEnumerator EndOfTutorialMessages()
    {

        DisplayMessage("Congratulation!

You’ve finished tutorial!

Press <color=red>E to continue", false);
yield return StartCoroutine(WaitForKeyDown(KeyCode.E));
yield return StartCoroutine(WaitForKeyDown(KeyCode.E));

        DisplayMessage("Here's some aditional tips for you

Press <color=red>E to continue", false);
yield return StartCoroutine(WaitForKeyDown(KeyCode.E));

        DisplayMessage("Press <color=red>R</color> at any time to restart level

Press <color=red>E to continue", false);
yield return StartCoroutine(WaitForKeyDown(KeyCode.E));
}

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

    public void DisplayMessage(string message, bool disable, float howLong = 0)
    {
        tutorialMessage.text = message;
        tutorialMessage.gameObject.SetActive(true);

        if (disable)
            Invoke("DisableTutorialMessage", howLong);
    }

As you can see, after the first “Congratulation!” message, I had to apply two calls to “WaitForKeyDown” coroutine, otherwise pressing E once, skips straight to the third “Press R…” message every time.

However, for second and third message this issue doesn’t exist, and pressing E once goes to the correct next message without skipping anything.

I’ll be thankful for any help with this.

I have an idea, that is to skip to the next frame in WaitForKeyDown()

IEnumerator WaitForKeyDown(KeyCode keyCode)
{
    while (!Input.GetKeyDown(keyCode))
        yield return null;
    yield return null; //needed to skip to the next frame where Input.GetKeyDown() will be false
}

Or you could try not start a coroutine, but instead insert the code directly

public IEnumerator EndOfTutorialMessages()
{
    DisplayMessage("Congratulation...", false);
    while (!Input.GetKeyDown(KeyCode.E)) yield return null;
    yield return null; //we need this to skip to the next frame or it will fall through 
    DisplayMessage("Here's some...", false);
    while (!Input.GetKeyDown(KeyCode.E)) yield return null;
    yield return null;
    //...

I think it is because StartCoroutine() does not start the coroutine until the next frame. That’s why it works if you put two of the WaitForKeyDown()

Another option is to get rid of the coroutines and do it in Update().

IEnumerator WaitForKeyDown(KeyCode keyCode)
{
while (!Input.GetKeyDown(keyCode))
yield return null;
yield return null; //needed to skip to the next frame where Input.GetKeyDown() will be false
}
Thank you! this is a solution I originally implemented, I just wasn’t aware that another yield return null is needed between while (!Input.GetKeyDown(keyCode))

If I understand correctly, without it, two Input.GetKeyDowns get executed in the same frame, hence the issue.

This is still a bit weird to me, but at least I understand the cause now.

Also, Another option is to get rid of the coroutines and do it in Update(), I’m trying to pust minimal amount of stuff in Update, since I’ve heard it can slow down the game.
Is that correct? Are coroutines more optimal solution?