Why Is This Coroutine Freezing my Unity5 editor?
I feel bad posting this question since it has been posted so many times in so many different ways,
Such as here: http://answers.unity3d.com/questions/838212/why-does-this-coroutine-freeze-my-game.html
And here: http://answers.unity3d.com/questions/586609/waiting-for-input-via-coroutine.html
And here also: https://www.reddit.com/r/Unity3D/comments/3hgg5c/wait_for_input_c/
But I have come to my wits end! Even when I copy the exact code I'm seeing on those pages, my Unity player still freezes as soon as I hit play. What I am trying to do is write a dirt-simple function that waits for a button input from the user, using a coroutine. I've tried several ways of doing this, and they all freeze my game:
private void SpaceBarToContinue () {
print ("Press spacebar to continue...");
StartCoroutine(WaitForButtonPress(KeyCode.Space));
}
/*
//This freezes the game
private IEnumerator WaitForButtonPress (KeyCode button) {
do {
yield return null;
} while (!Input.GetKeyDown (button));
}
//This also freezes the game
private IEnumerator WaitForButtonPress (KeyCode button) {
while (true) {
if(Input.GetKeyDown (button))
yield break;
yield return null;
}
}
//Surprise! This freezes the game
private IEnumerator WaitForButtonPress (KeyCode button) {
while (true) {
if(!Input.GetKeyDown (button))
yield return null;
yield break;
}
}
*/
//Hey maybe this time- nope! This freezes the game
private IEnumerator WaitForButtonPress (KeyCode button) {
while (true) {
if(!Input.GetKeyDown (button))
yield break;
}
yield return null;
}
When I run the debug, the program just keeps cycling forever, never giving me the opportunity to end the cycle with button input. I've force-quit about 20 times now trying lots of different variations on the above, and I've finally just decided to ask for help.
Can someone please clue me in to as what simple thing I must be missing here? I would greatly, greatly appreciate any assistance on this matter.
Answer by csalzman · Aug 08, 2016 at 02:43 PM
Tried this myself and ran into an issue using "break" to get out of the loop. A more readable way to handle this is to set a bool inside of the coroutine itself and check against that. Here's a C# script that worked on a Unity 5.4 project:
using UnityEngine;
using System.Collections;
public class TestingCoroutine : MonoBehaviour {
void Start () {
//Start up and run the SpaceBarToContinue
SpaceBarToContinue ();
}
private void SpaceBarToContinue () {
print ("Press spacebar to continue...");
StartCoroutine(WaitForButtonPress(KeyCode.Space));
}
private IEnumerator WaitForButtonPress (KeyCode button) {
//Set your own toggle
bool waiting = true;
//While loop based on this toggle being true
while (waiting) {
//Check for "button" being pressed down. In this case KeyCode.Space
if(Input.GetKeyDown (button)) {
//It was pressed, toggle waiting off so the coroutine will end
waiting = false;
//Do what you want now
Debug.Log("SPACE BAR PRESSED. DO WHAT YOU WANT");
}
//If the space bar wasn't pressed, wait for the next fixedupdate and check again
yield return new WaitForFixedUpdate();
}
}
}
Hi czalman, thank you so much for contributing, and even taking the trouble to write out and test code! I think I finally understand what is going wrong.
What I was hoping to do was to create a generic SpaceBarToContinue() function that I could use anytime I ever wanted to have the user press space bar to continue. Whether it was to acknowledge they had read something, or to move to the next step in state machine, or anything. I thought I could accomplish this using a coroutine.
However now I realize that using a coroutine isn't a way to allow your game to "wait" without freezing. Ins$$anonymous$$d, it will tell the game to keep waiting for spacebar, but continue the main program (outside of SpaceBarToContinue() function) anyway.
That means that the only way I can have something specific happen when I press spacebar is to place it within SpaceBarToContinue(), inside the if(Input.Get$$anonymous$$eyDown(button)) check. Furthermore, that means I cannot create a generic SpaceBarToContinue() function the way I had originally envisioned. Ins$$anonymous$$d, I would need to create a separate coroutine function with custom functionality for every time that I want to wait for an input from the user.
I suppose, hypothetically, I could create a WaitForSpaceBar() function which accepts a delegate function as a parameter, and that way could activate any given function using WaitForSpaceBar(DelegateFunction), but that seems like bad design.
I think I will just have to go back to the drawing board and figure out a way to make my game more event-driven, and less sequential, so that I avoid the issue of infinite looping altogether.
If you disagree with anything I'm saying, or you think there is something I am missing, I would gladly listen to any advice you have to offer.
Thank you again for your help!
No problem! $$anonymous$$y first inclination is that a coroutine probably isn't what you're looking for--although I really do like coroutines a lot. I'm wondering if you should consider some sort of global "pause" boolean that most or all of your game objects reference. That way if you need to pause the game you can flip that boolean to true, show what you need to, and wait for the player to hit space bar.
Something like this within Update():
if(pause == true && Input.Get$$anonymous$$eyDown ($$anonymous$$eyCode.Space)) { pause = false; //Do other things when you unpause like clear your message for "hit space bar to continue") }
But, that is making a lot of assumptions on what you're going to be using this for :) Good luck! You've got this!
Answer by flaviusxvii · Aug 03, 2016 at 09:52 PM
It's freezing because you've written an infinite loop that never hands over control back to the main thread. Coroutines run on the MAIN THREAD. So if you keep it locked in a loop nothing else can happen.
try this:
private IEnumerator WaitForButtonPress (KeyCode button) {
while (true) {
if(!Input.GetKeyDown (button)) {
break;
}
// Release the thread until the next game loop.
yield return new WaitForFixedUpdate();
}
}
Hi Flavius, thank you so much for the assist! That definitely solved the first part of my problem, the program is no longer immediately crashing. However it's not waiting for a button press either; it just breaks out of the while loop immediately and continues the program.
I tried removing the exclamation point (so that it reads "if (Input.Get$$anonymous$$eyDown(button))"), but of course, that just created another infinite loop for me to get stuck in, and the program froze.
I also tried breaking out of the loop if the button was pressed, and yielding otherwise:
private IEnumerator WaitForButtonPress ($$anonymous$$eyCode button) {
while (true) {
if (Input.Get$$anonymous$$eyDown (button))
break;
else
yield return new WaitForFixedUpdate ();
}
}
But that also just completed the program without waiting for a button press. Again, there must be something here I'm missing.
The 'break' is going to exit the loop which will exit the co-routine. What do you want to happen when a button is pressed. You'd put that where the 'break' currently is if you want to happen over and over. If you want something happen just once, then put it after the 'while' loop.
What I'm looking to have happen is this:
Program is started
function WaitForbuttonPress(spacebar) is called
The program prints "press spacebar to continue"
program waits
program waits
program waits
etc.
The user (me) presses spacebar
The program continues
That's all. But the only two things I've been able to accomplish is either A) an infinite loop that freezes the program, or B) exiting the WaitForButtonPress() function before I press any button.