i'm very new to programming and i have a feeling that there is a very stupid mistake here. But can anyone explain me, why instead of 4 messages with a delay of 2 seconds between, i instantaniously get the last message shown only.
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using UnityEngine.UI;
public class Wait : MonoBehaviour {
private int i = 0;
public string[] message;
[SerializeField]
private Text toText;
public IEnumerator Message(float waitTime)
{
toText.text = message[i];
i++;
yield return new WaitForSeconds(waitTime = 2f);
}
void Start()
{
StartCoroutine(Message(i));
StartCoroutine(Message(i));
StartCoroutine(Message(i));
StartCoroutine(Message(i));
}
}
void Start()
{
StartCoroutine(Message(i));
StartCoroutine(Message(i));
StartCoroutine(Message(i));
StartCoroutine(Message(i));
}
I don't think that is doing what you think it should. This will not wait for each StartCoroutine to finish and will call the next StartCoroutine.
This is what happens:
The first StartCoroutine(Message(i)); call will start the Message function.
Once it meets the yield return new WaitForSeconds(waitTime = 2f); line of code, it will then jump back into the Start() function.
The next StartCoroutine(Message(i)); will be called then the-same thing will happen again.
When calling a coroutine function from a non coroutine function, as long as you have yield return new WaitForSeconds, yield return null;, or yield return what-ever YieldInstruction is implemented, the execution will return to that non coroutine function in-which the StartCoroutine function was called from and continue to execute other code.
To make coroutine wait for another one to finish, make the StartCoroutine(Message(i)); function call from another coroutine function. This will allow you to yield each coroutine function call. This is called chaining coroutine.
To chain or yield a coroutine function call, simply put yield return in front of the StartCoroutine function. yield return StartCoroutine(Message(i));
public class Wait : MonoBehaviour {
private int i = 0;
public string[] message;
[SerializeField]
private Text toText;
public IEnumerator Message(float waitTime)
{
// toText.text = message[i];
i++;
yield return new WaitForSeconds(waitTime = 2f);
}
void Start()
{
StartCoroutine(startMessage());
}
IEnumerator startMessage()
{
yield return StartCoroutine(Message(i));//Wait until this coroutine function retuns
yield return StartCoroutine(Message(i));//Wait until this coroutine function retuns
yield return StartCoroutine(Message(i));//Wait until this coroutine function retuns
yield return StartCoroutine(Message(i));//Wait until this coroutine function retuns
}
}
Now, each StartCoroutine(Message(i)); call will wait until the first one finishes. You can always use a boolean variable to do this but it is much better to yield the StartCoroutine call.
The reason the text is set instantly is because StartCoroutine will execute the enumerator from Message.
The first two things that happen is setting the text and incrementing i. Only after that is done, you will yield the WaitForSeconds. It is at that point StartCoroutine will pause further execution of Message.
If you had had a line after the yield return you would have seen the effects of that after the 2 seconds.
in the example in the docs you can also see the behavior after the yield return Wait
https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html
I would suggest running the next test to become more familiar with how yield return works:
IEnumerator MessageOuter() {
Console.WriteLine("outer 1");
var inner = MessageInner();
Console.WriteLine("outer 2");
return inner;
}
IEnumerator MessageInner() {
Console.WriteLine("inner 1");
yield return new WaitForSeconds(1);
Console.WriteLine("inner 2");
yield return new WaitForSeconds(1);
Console.WriteLine("inner 3");
}
void Start() {
Console.WriteLine("start 1");
var outer = MessageOuter();
Console.WriteLine("start 2");
StartCoroutine(outer);
Console.WriteLine("start 3");
}
Related
I am calling a method that call itself to crawl through terrain and create zones. However when the zones become to large the process ends in a stack overflow. By forcing the code to yield and take its time it finishes to completion successfuly and crawls the 3 zones in my map. However the method I am using is yielding EVERY single frame and I don't know how to make it yield just every 100 frames, causing it to be extremely slow to finish. Here is the pseudo code of what I am doing for readability:
public int timer = 0;
void Awake(){
StartCoroutine(crA);
}
public IEnumerator crA(){
//stuff
yield return StartCoroutine(crB());
//stuff that needs to happen only after crB finishes
}
public IEnumerator crB(){
timer = 0;
yield return StartCoroutine(crC());
}
public IEnumerator crC(){
//Crawiling code, crawls terrain to create a zone
if(x){ yield break;}
timer++;
//vv I WANTED IT TO YIELD ONLY IN HERE
if (timer ==100){
timer = 0;
yield return null;
}
//^^
yield return StartCoroutine(crC());
yield return StartCoroutine(crC());
yield return StartCoroutine(crC());
yield return StartCoroutine(crC());
yield return StartCoroutine(crC());
}
it seems yield return startcoroutine is causing a yield but I don't know what to use instead. Any help would be appreciated.
As said in general as long as you use yield it will at least yield for one frame like
yield return null;
does.
What you could do instead is use the IEnumerator.MoveNext "manually" without yielding it. This is basically what the Unity coroutine is calling once per frame if you used StartCoroutine.
And then only yield when you want to do so. Given your pseudo code something like e.g.
private void Awake()
{
StartCoroutine(crA);
}
public IEnumerator crA()
{
//stuff
yield return crB();
//stuff that needs to happen only after crB finishes
}
public IEnumerator crB()
{
timer = 0;
// "Manually" run the routine instead of using Unity Coroutine interface
var crc = crC();
while(crc.MoveNext())
{
if(timer >= 100)
{
yield return null;
timer = 0;
}
}
}
public IEnumerator crC()
{
//Crawiling code, crawls terrain to create a zone
if(x) yield break;
timer++;
yield return crC();
yield return crC();
yield return crC();
yield return crC();
yield return crC();
}
And then as said you could do it time based instead of using a frame/call counter rather using e.g. a StopWatch
private const float TARGET_FRAMERATE = 60;
public IEnumerator crB()
{
var targetMilliseconds = 1000f / TARGET_FRAMERATE;
var sw = new StopWatch();
sw.Start();
// "Manually" run the routine instead of using Unity Coroutine interface
var crc = crC();
while(crc.MoveNext())
{
if(sw.ElapsedMilliseconds >= targetMilliseconds)
{
yield return null;
sw.Restart();
}
}
}
So whenever the last execution exceeded the target frame-rate it will yield one frame. You'll have to play a bit with the value .. probably something like 30 or even 24 might already be enough depending on your usecase.
It basically is a tradeoff between frame-rate and actual realtime duration.
Note: Typed on smartphone but I hope the idea gets clear
I wrote a script to spawn game object in my unity game. It uses coroutine. Why my spawnWaves() is called just once? Here is my script:
using UnityEngine;
using System.Collections;
public class GameController : MonoBehaviour {
public GameObject[] hazards;
public Vector3 spawnValues;
public float spawnInterval;
// Use this for initialization
void Start ()
{
StartCoroutine(SpawnWaves(spawnInterval));
}
IEnumerator SpawnWaves(float interval)
{
Vector3 spawnPosition = new Vector3(Random.Range(-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
Quaternion spawnRotation = new Quaternion(Random.Range(-Mathf.PI, Mathf.PI), Random.Range(-Mathf.PI, Mathf.PI), Random.Range(-Mathf.PI, Mathf.PI), Random.Range(-1f, +1f));
Instantiate(hazards[Random.Range(0, 3)], spawnPosition, spawnRotation);
yield return new WaitForSeconds(interval);
}
}
All public values inside of Unity Editor is set correctly.
What's wrong?
Thanks!
A couroutine is nothing else than a normal yield-method.
For having a loop use while.
while (...)
{
// Do work here
yield return new WaitForSeconds(interval);
}
As alternative, which i do not recommend, you can start the coroutine again at the end.
....
yield return new WaitForSeconds(interval);
StartCoroutine(SpawnWaves(spawnInterval));
Further explaination
A coroutine uses a enumerator for running the above code. Basically the compiler creates a background class which implements the IEnumerator interface. Jon Skeet explains it very well here.
The problem is that your method has only one occurrence of yield return and it's not in a loop. This means that it always returns an IEnumerable object that contains (yields) a single item from start to end.
The question you have to be able to answer is: how many items do you expect the coroutine to yield?
If you expect it to yield only one item, then it's fine as it is.
If you expect it to yield (for example) 3 items, it would look something like this:
yield return new WaitForSeconds(interval);
yield return new WaitForSeconds(interval);
yield return new WaitForSeconds(interval);
If you expect it to yield n items, it would look something like:
for(var i = 0; i < n; i++)
{
yield return new WaitForSeconds(interval);
}
If you expect it to yield until a condition is met, then you probably want something like this:
while(!conditionIsMet)
{
yield return new WaitForSeconds(interval);
}
And if you expect it to yield a potentially infinite amount of items:
while(true)
{
yield return new WaitForSeconds(interval);
}
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);
}
}
}
I know that Unity3D StartCoroutine calls a function which runs on the same thread as StartCoroutine, but when does the called function return back to the original caller?
I looked around the internet for a good Unity3D Coroutine example and couldn't find a complete one. There is a great explanation by UnityGems, but even their example is incomplete. So I wrote my own example.
This:
using UnityEngine;
using System.Collections;
public class MainCamera: MonoBehaviour {
void Start () {
Debug.Log ("About to StartCoroutine");
StartCoroutine(TestCoroutine());
Debug.Log ("Back from StartCoroutine");
}
IEnumerator TestCoroutine(){
Debug.Log ("about to yield return WaitForSeconds(1)");
yield return new WaitForSeconds(1);
Debug.Log ("Just waited 1 second");
yield return new WaitForSeconds(1);
Debug.Log ("Just waited another second");
yield break;
Debug.Log ("You'll never see this"); // produces a dead code warning
}
}
Produces this output:
About to StartCoroutine
about to yield return WaitForSeconds(1)
Back from StartCoroutine
Just waited 1 second
Just waited another second