Using a Coroutine in a loop - c#

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" ;)

Related

Trouble understanding Delays and Coroutines

void start()
StartCoroutine(Text());
IEnumerator Text()
{
Debug.Log("Hello")
yield return new WaitForSeconds(3)
Debug.Log("ByeBye")
}
I understand the basic concept that this does but I don't get what anything means such as yield return new WaitforSeconds(3) and what StartCoroutine is and What an IEnumerator is.
Can anyone explain to me what they mean?
When you call a function, it runs to completion before returning. This effectively means that any action taking place in a function must happen within a single frame update; a function call can’t be used to contain a procedural animation or a sequence of events over time. As an example, consider the task of gradually reducing an object’s alpha (opacity) value until it becomes completely invisible.
void Fade()
{
for (float ft = 1f; ft >= 0; ft -= 0.1f)
{
Color c = renderer.material.color;
c.a = ft;
renderer.material.color = c;
}
}
As it stands, the Fade function will not have the effect you might expect. In order for the fading to be visible, the alpha must be reduced over a sequence of frames to show the intermediate values being rendered. However, the function will execute in its entirety within a single frame update. The intermediate values will never be seen and the object will disappear instantly.
It is possible to handle situations like this by adding code to the Update function that executes the fade on a frame-by-frame basis. However, it is often more convenient to use a coroutine for this kind of task.
A coroutine is like a function that has the ability to pause execution and return control to Unity but then to continue where it left off on the following frame. In C#, a coroutine is declared like this:
IEnumerator Fade()
{
for (float ft = 1f; ft >= 0; ft -= 0.1f)
{
Color c = renderer.material.color;
c.a = ft;
renderer.material.color = c;
yield return null;
}
}
It is essentially a function declared with a return type of IEnumerator and with the yield return statement included somewhere in the body. The yield return null line is the point at which execution will pause and be resumed the following frame. To set a coroutine running, you need to use the StartCoroutine function:
void Update()
{
if (Input.GetKeyDown("f"))
{
StartCoroutine("Fade");
}
}
You will notice that the loop counter in the Fade function maintains its correct value over the lifetime of the coroutine. In fact any variable or parameter will be correctly preserved between yields.
By default, a coroutine is resumed on the frame after it yields but it is also possible to introduce a time delay using WaitForSeconds:
IEnumerator Fade()
{
for (float ft = 1f; ft >= 0; ft -= 0.1f)
{
Color c = renderer.material.color;
c.a = ft;
renderer.material.color = c;
yield return new WaitForSeconds(.1f);
}
}
This can be used as a way to spread an effect over a period of time, but it is also a useful optimization. Many tasks in a game need to be carried out periodically and the most obvious way to do this is to include them in the Update function. However, this function will typically be called many times per second. When a task doesn’t need to be repeated quite so frequently, you can put it in a coroutine to get an update regularly but not every single frame. An example of this might be an alarm that warns the player if an enemy is nearby. The code might look something like this:
bool ProximityCheck()
{
for (int i = 0; i < enemies.Length; i++)
{
if (Vector3.Distance(transform.position, enemies[i].transform.position) < dangerDistance) {
return true;
}
}
return false;
}
If there are a lot of enemies then calling this function every frame might introduce a significant overhead. However, you could use a coroutine to call it every tenth of a second:
IEnumerator DoCheck()
{
for(;;)
{
ProximityCheck();
yield return new WaitForSeconds(.1f);
}
}
This would greatly reduce the number of checks carried out without any noticeable effect on gameplay.
Note: You can stop a Coroutine with StopCoroutine and StopAllCoroutines. A coroutines also stops when the GameObject it is attached to is disabled with SetActive(false). Calling Destroy(example) (where example is a MonoBehaviour instance) immediately triggers OnDisable and the coroutine is processed, effectively stopping it. Finally, OnDestroy is invoked at the end of the frame.
Coroutines are not stopped when disabling a MonoBehaviour by setting enabled to false on a MonoBehaviour instance.
Reference: https://docs.unity3d.com/Manual/Coroutines.html
Unity (ab)uses enumerators to build C# CoRoutines, because async / await didn't exist. When you write;
IEnumerator Text()
{
Debug.Log("Hello")
yield return new WaitForSeconds(3)
Debug.Log("ByeBye")
}
The compiler turns that into something like;
IEnumerator Text() => new StateMachine();
public class StateMachine : IEnumerable{
private int state = 0;
// plus any local variables moved to fields.
StateMachine(){}
public object Current { get; set; }
public bool MoveNext(){
switch(state){
case 0:
Debug.Log("Hello");
Current = new WaitForSeconds(3);
state = 1;
return true;
case 1:
Debug.Log("ByeBye");
return false;
}
}
}
Since the state of your function is now stored in fields on an object, your method can pause before it finishes. Unity will then look at the object you yield to decide when to call MoveNext().
Now that C# has async methods, which also cause your methods to be translated into state machines. It would be possible for a new version of unity to support them instead, like;
async Task Text()
{
Debug.Log("Hello")
await Something.WaitForSeconds(3)
Debug.Log("ByeBye")
}
But they would still have to support the old way of building CoRoutines.

How to clear all user keystrokes in unity c#?

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;
}
}
}

End Coroutine if one of two conditions becomes true

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
}
}

Second Coroutine isn't working Unity

I am making 2D game and want to cause delay of about 3 sec after player's all lives ran out. I tried to implement Coroutine method before the scene start all over again but it doesn't work.
I have already implemented Coroutine method for each time my player falls of a cliff and respawn back to its position. And it works like a charm.
public void Respawner()
{
StartCoroutine("RespawnCoroutine");
}
// Coroutine Delay of 2 sec for each time player Respawn
public IEnumerator RespawnCoroutine()
{
classobj.gameObject.SetActive(false);
yield return new WaitForSeconds(respawnDelaySec);
classobj.transform.position = classobj.respawnPoint;
classobj.gameObject.SetActive(true);
}
public void ReduceLives()
{
if (lives <= 3 && lives >= 2)
{
lives--;
live_text.text = "Remaining Live " + lives;
}
else
{
StartCoroutine("RestartScene1");
}
}
public IEnumerable RestartScene1()
{
yield return new WaitForSeconds(RestartSceneDelaySec);
SceneManager.LoadScene("demo2");
}
here is no error on console window but SceneManager.LoadScene("demo2"); is never called and the player is respawning each time after i die and after 1 life remaining
The issue with your second coroutine is..
You have mistakenly used "IEnumerable" instead "IEnumerator", make it change to "IEnumerator" and it will work..
You should not call SceneManager.LoadScene("demo2"); after StartCoroutine("RestartScene1");.
StartCoroutine("RestartScene1"); this code you can say is an asynchronous code. It is called, and the execution of program keeps going forward (the execution does not await here). You should call the code you want to delay inside that Coroutine after yielding.
Small exampel:
public void SomeFunction()
{
StartCoroutine("RestartScene1");
// The code here will **not** be delayed
}
public IEnumerable RestartScene1()
{
yield return new WaitForSeconds(RestartSceneDelaySec);
// The code here will be delayed
}

Loops using coroutines

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);
}
}
}

Categories

Resources