I'm not fully understanding coroutines.
If I have a coroutine that does something each frame, how do I end this looping behaviour if either of two conditions become true?
And when I say end, I also mean destroy, not put into a holding state or other paused or non completed state, so I can then restart the coroutine if a different condition becomes true.
If the conditions exist outside of the coroutine, you use StopCoroutine:
var coroutine = StartCoroutine(MyRoutine());
...
if (conditionA || conditionB) {
StopCoroutine(coroutine);
}
If the conditions exist inside of the coroutine, you just yield break:
IEnumerator MyRoutine() {
while (true) {
if (conditionA || conditionB) {
yield break; // stop stepping this
}
yield return null; // continue stepping next frame
}
}
Related
This has been fustrating me as a beginner/learning coder. I've been trying to make it so when my game finishes loading using an async method, it loads a button so you can continue. Instead, it ignores the function altogether, causing the button to still be there ingame and glitch out when you press it too early. I have my code right here; how can I fix this?
IEnumerator LoadSceneAsynchronously(int levelIndex)
{
AsyncOperation operation = SceneManager.LoadSceneAsync(levelIndex);
while (!operation.isDone)
{
Debug.Log(operation.progress);
loading.GetComponent<Animator>().Play("loading");
text.GetComponent<Animator>().Play("idle");
yield return null;
}
if (!operation.isDone)
{
yield return new WaitForSeconds(5);
loading.GetComponent<Animator>().Play("loading disappear");
text.GetComponent<Animator>().Play("appear text");
text.GetComponent<Animator>().Play("blink text")
yield return null;
}
}
public void DestroyLoading()
{
gameObject.SetActive(false);
GameIsDoneLoading = true;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space) && !GameIsDoneLoading == true)
{
Debug.Log("Space key was pressed.");
text.GetComponent<Animator>().Play("disappear text");
logo.GetComponent<Animator>().Play("fpsa logo disappear");
}
}
When you 'yield' in a Coroutine, code execution returns to the exact same place in the next frame, so in the while loop above it just checks that condition every frame, then yields to the next frame. I believe what you want to do is...
IEnumerator LoadSceneAsynchronously(int levelIndex)
{
// start the async loading
AsyncOperation operation = SceneManager.LoadSceneAsync(levelIndex);
// start playback of your animations in the scene
loading.GetComponent<Animator>().Play("loading");
text.GetComponent<Animator>().Play("idle");
// now wait for the scene to fully load
while (!operation.isDone)
{
Debug.Log(operation.progress);
yield return null;
// after yielding, we return here and repeat the while
// loop on the next frame
}
// we have broken free of the while loop so the level has now fully loaded
// and operation.isDone is now true
// play some other animations to fade out the loading system?
loading.GetComponent<Animator>().Play("loading disappear");
text.GetComponent<Animator>().Play("appear text");
text.GetComponent<Animator>().Play("blink text")
// I'm assuming these animations take about 5 seconds to complete?
// so just wait in here for 5
yield return new WaitForSeconds(5);
// loading level and animations all done...proceed to games
DestroyLoading();
}
I have a question about coroutines in a loop. More specific about how I can achieve it that my loop continues to check the condition until the waitForSeconds in the coroutine are over.
I have attached a screenshot of my code. My problem is that the Line "Now I am executed" is shown right after myAudio.Play();
It makes sense since the coroutine only waits for the statements after the yield return but how can I make it work with the for loop?
Any help would be appreciated! Thank you so much.
StartCoroutine starts each IEnumeratoras a new Coroutine and immediately continuous with the rest of the code.
So as said without having further context of your code what you would do is something like
private void playDBStack()
{
fillList();
StartCoroutine(playDBStackRoutine())
}
private IEnumerator playDBStackRoutine()
{
for(var i = 0; i < 2; i++)
{
// executes playAudio and waits for it to finish
yield return playAudio();
}
}
private IEnumerator playAudio()
{
var x = chooseList(score);
// executes PlayAndWait and wait for it to finish
yield return PlayAndWait(x);
}
this way you execute and at the same time wait until the IEnumerators are finished before going to the next one.
Some further notes:
The line
myAudio.GetComponent<AudioSource>();
does absolutely nothing.
The entire method updateScore in its current state could simply be replaced by using
score++;
I would consider a method that can be replaced by a simple operator "code smell" ;)
i'm writing some c# code within Unity game i'm developing.
and there's a problem i can't fix.
i'm using StartCoroutine in my code and inside i'm calling another StartCoroutine. i know that when doing that, there's two threads executing the code in those parts.
but not if i'm calling yield return to that StartCorotuine;
IEnumerator StartLoop()
{
yield return StartCorotuine(GetInputFromUser()); // 1
// some variables
yield return StartCorotuine(GetInputFromUser()); // 2
}
IEnumerator GetInputFromUser()
{
if (Input.GetKeyDown(KeyCode.Space))
{
print("IN");
}
else
{
yield return null;
}
}
the problem is, it prints twice.
the buffer isn't empty from the first time calling "GetInputFromUser()" and it keeps it until the second time and enters immediately to the if even when i'm not pressing Space.
things i've tried:
Console.Clear();
Console.ReadKey();
Console.ReadLine();
UnityEngine.Rendering.CommandBuffer.Clear();
while(Console.KeyAvailable)
Console.ReadKey();
and some more i can't even remember.
either the whole idea is wrong and somehow it worked till now. or i'm missing something.
any advise? i've googled everything i could think of.
[[[SOLVED]]]
I don't know how it works here, and if you're closing the post or something. but I've solved it.
The problem was, as someone suggested, that the calls happened in the same frame, so i've added another null in between to force skipping to the next frame:
IEnumerator StartLoop()
{
yield return StartCorotuine(GetInputFromUser()); // 1
// some variables
yield return null; // skipping frame
yield return StartCorotuine(GetInputFromUser()); // 2
}
IEnumerator GetInputFromUser()
{
if (Input.GetKeyDown(KeyCode.Space))
{
print("IN");
}
else
{
yield return null;
}
}
Thanks for the helpers!
First of all: Coroutines have nothing to do with threads! Every Coroutine is executed in the Unity main thread and get their MoveNext executed right after Update has finished for that behaviour.
Your problem should be that in
IEnumerator GetInputFromUser()
{
if (Input.GetKeyDown(KeyCode.Space))
{
print("IN");
}
else
{
yield return null;
}
}
in the case there was no input you do yield return null; which causes the Coroutine to wait at least one frame!
So the second one is executed in the next frame.
But in case there was an input you do not wait so the next Coroutine is directly started where again you don't wait!
Result: You get your print twice without waiting at all.
It's not fully clear what you are trying here but I assume you want to wait until Space is pressed so you should rather use e.g.
bool GetInputFromUser()
{
if (Input.GetKeyDown(KeyCode.Space))
{
print("IN");
return true
}
return false;
}
And use it like
// This now waits until the user presses Space
yield return new WaitUntil (GetInputFromUser);
or simply also yield for one frame in case there was the input like
IEnumerator GetInputFromUser()
{
if (Input.GetKeyDown(KeyCode.Space))
{
print("IN");
}
yield return null;
}
though
the goal is to check if the user presses the SpaceBar more than once.
It is very very unlikely that a user manages to press the key down twice in two consecutive frames.
StartLoop starts inside Start()
but at this moment the user can't provide any input yet anyway. So you check only once if the user presses a key within the first frames..
In Start()
I'm waiting for the first Space stroke.
once he has, I'm starting the StartCorotuine(StartLoop()); sorry for the mixup.
I'm using it like that, maybe it'll be more clear, once the user Starting the loop, I'm waiting for him to press Space. once he has a timer is set on and once the timer has timed the IEnumerator return without Input from the User and I'm getting to the next iteration, the only thing I want to do is to check if the user pressed twice on that Space.
{
while (timer < timeSetInStart)
{
If(Input.GetKeyDown(KeyCode.Space))
{
print("IN";
yield break;
}
else
{
yield return null;
}
}
}
I'm not sure if there's a specific reason why this needs to be in a coroutine but it might be easier to handle this all in Update():
bool spacePressed = false;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
if (spacePressed)
{
print("Space pressed twice");
spacePressed = false;
}
else
{
print("Space pressed once");
spacePressed = true;
}
}
}
When two co-routines are running, how do you stop the first co-routine?
GLOBALS.stableTime = 5;
IEnumerator StableWaittingTime ()
{
yield return new WaitForSeconds (1f);
if (GLOBALS.stableTime == 0) {
GameManager.instance.LevelFaildMethod ();
} else {
GameManager.instance.stableWaittingTime.text = GLOBALS.stableTime.ToString ();
GLOBALS.stableTime--;
StartCoroutine ("StableWaittingTime");
}
}
There are three ways to stop coroutines.
The first is to call StopAllCoroutines(), which will obviously stop all running coroutines.
The second is to call StopCoroutine(coroutine), where coroutine is a variable name given to your IEnumerator.
And the third is to do a yield break from within the coroutine.
Worth noting is that both StopAllCoroutines and StopCoroutine can only stop a coroutine when the coroutine reaches a yield return *.
So if you have two coroutines with the same name and you want to stop the one you are executing in you do yield break.
Interestingly, if you want to stop every other coroutine besides the one you are executing in, you call StopCoroutines() from within that coroutine.
#Imapler answer is almost all you need. I would just add that StopCoroutine method of MonoBehaviour is overloaded and has 3 types of parameters, so it is possible to stop many coroutines of same name.
For your need here, just use yield break; like this:
void Start ()
{
StartCoroutine (StableWaittingTime ());
}
IEnumerator StableWaittingTime ()
{
yield return new WaitForSeconds (1f);
if (false)
{
// do something
}
else
{
// do something
StartCoroutine (StableWaittingTime ());
yield break;
}
}
I have a question about coroutine behaviour in case of loops, see following code extract as example done on a Unity C# script:
void Start() {
StartCoroutine(FSM());
}
IEnumerator FSM() {
state="State1";
while (true) {
Debug.Log("State "+state);
yield return StartCoroutine(state);
}
}
IEnumerator State1() {
while (true) {
if (stateTransitionCond) {
state = "NextState";
yield break;
}
yield return null;
}
}
The status machine works fine, but while the current status is Status1 (stateTransitionCond==false), due to the yield return null inside the loop of State1() routine, I was expecting that loop inside FMS() also performs another iteration generating debug log 'Debug.Log("State "+state);'.
In other words I was expecting a lot of debug log (one for each iteration of State1() routine, when status is Status1) but in the reality only 1 execution is performed while status is Status1.
So I suppose I miss something about yield functionality, is there anybody that can explain me this behaviour?
Your issue stems from the fact that your code does not break out of the State1() method until stateTransitionCond == true.
The method starting the coroutine, FSM(), is not returned to until State1 is finished. In other words, control flow does not return to the calling method until the coroutine is complete. I believe this is due to the fact that you are yield-ing State1 inside FSM (yielding another coroutine). Obviously, "normal" methods do not wait for the coroutine to finish before continuing execution.
Please see the code sample below for an illustrative example:
using UnityEngine;
using System.Collections;
public class CoroutineTest : MonoBehaviour {
// current FSM state
public string state = "";
void Start()
{
StartCoroutine(FSM());
}
IEnumerator FSM()
{
state = "State1";
while (true)
{
Debug.Log("State: " + state);
// ExecuteOnce will execute exactly once before returning to the outer function
yield return StartCoroutine(ExecuteOnce());
// ExecuteIndefinitely will execute indefinitely until its while() loop is broken
// uncomment to test
//yield return StartCoroutine(ExecuteIndefinitely());
}
}
IEnumerator ExecuteOnce()
{
Debug.Log("Calling ExecuteOnce()");
yield return new WaitForSeconds(1f);
}
IEnumerator ExecuteIndefinitely()
{
Debug.Log("Calling ExecuteIndefinitely()");
while (true)
{
Debug.Log("Inside ExecuteIndefinitely()");
yield return new WaitForSeconds(1f);
}
}
}