- Home /
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!\nYou've finished tutorial!\n\nPress <color=red>E</color> to continue", false);
yield return StartCoroutine(WaitForKeyDown(KeyCode.E));
yield return StartCoroutine(WaitForKeyDown(KeyCode.E));
DisplayMessage("Here's some aditional tips for you\n\nPress <color=red>E</color> to continue", false);
yield return StartCoroutine(WaitForKeyDown(KeyCode.E));
DisplayMessage("Press <color=red>R</color> at any time to restart level\n\nPress <color=red>E</color> 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.
Answer by rh_galaxy · May 07 at 05:19 AM
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().
Answer by KoRnish7 · May 07 at 11:34 AM
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?
I would say the opposite, what you do in Update() runs "as is", while what you do with Coroutines are added to a list when you call StartCouroutine() then executed the next frame until the first yield, so it is more complex, not even sure that's the whole story.
yield return StartCoroutine(WaitForKeyDown(KeyCode.E)); // this will pause here
// and run this code next frame:
next frame-> while (!Input.GetKeyDown(keyCode)) yield return null; //this waits
// until the frame when E is sensed
directly after-> Input.GetKeyDown(keyCode) is still true the entire frame we need
// to wait until the next frame before going further
yield return null; //this fixes so we wait one frame, and fixes the issue...
// not sure why it wasn't needed two times before all DisplayMessage()...
Your answer
Follow this Question
Related Questions
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
Check if a method hasn't been used for one second 1 Answer
Unity3D and C# - Coroutines vs threading 4 Answers