Coroutines are a great way of:
- Making things happen step by step
- Writing routines that need to happen over time
- Writing routines that have to wait for another operation to complete
See some more usage examples there.
For example you could easily imagine using a coroutine to orchestrate a cut scene sequence – or simply wait for an enemy to animate dying, pause and then respawn.
Threads and Coroutines
First of all, coroutines are not threads and coroutines are not asynchronous.
A thread is a process that runs asynchronously with the other threads in the program. On a multiprocessor machine, a thread can really be running its code at the same time as all of the other threads. This makes programming threads a complicated thing to get right, because one thread could be changing something at exactly the same time as another thread is reading it. This is even more complicated because the code you write is turned into assembly language by the compiler – which means another thread could actually change something in the middle of the game processing what appears to be a single line of your source code. Because of this you have to go through hoops to ensure that such circumstances don’t occur by either making sure that there is no shared memory or by locking other threads out of shared values while they are being read or changed.
So what is a Coroutine?
Since coroutine is not multi-thread it will be executing on the main thread of the game, it will then be the only thing in the core game code running at that time.
So here is the definition of what a coroutine could be:
A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done.
The start of a coroutine corresponds to the creation of an object of type coroutine. That object is tied to the MonoBehaviour component that hosted the call.
A bit more on that last line, you can start a coroutine from any object even non MonoBehaviour but it has to be called as a MonoBehaviour member. The lifetime of the Coroutine object is bound to the lifetime of the MonoBehaviour object, so if the latter gets destroyed during process, the coroutine object is also destroyed. We shall demonstrate that topic later on.
Unity processes coroutines every frame of the game for every object that has one or more running. The processing occurs after Update and before LateUpdate for most yield statements, but there are special cases:
When the coroutine is activated it will execute right up to the next yield statement and then it will pause until it is resumed. You can see where it will resume in the diagram above, based on what you yield.
private void Start() { Debug.Log("Start method"); StartCoroutine(TestCoroutine()); Debug.Log("Start method ends"); } private IEnumerator TestCoroutine() { Debug.Log("TestCoroutine"); while(true) { Debug.Log("HereW); yield return null; Debug.Log("There"); } } private void Update(){Debug.Log("Update");}
It logs as:
Start method
TestCoroutine
Here
Start method ends
Update
There
Here
Update
There
Here
…
It runs until it hits a yield. At this point, the coroutine is placed on the coroutine stack of the MonoBehaviour object, then back to the calling method and on with the program. Update is called and returns, then the program looks in the MonoBehaviour list of coroutine and finds our pending TestCoroutine and gets back where it left off, the yield statement. The same is processed over and over again until the end of the coroutine if the while condition is not met or the end of the MonoBehaviour object.
The code inside the loop is exactly like an Update function. It runs once every frame for this object, just after the script’s Update routine runs (if it has one).
When you call StartCoroutine(TestCoroutine()) the code executes immediately up to the first time it yields, it will then be resumed when Unity processes coroutines for this object. The call is actually short for this.StartCoroutine clearly indicating that the call is done from the MonoBehaviour component.
If you expect the Start to wait for the end of the coroutine before it finishes, consider the following:
private IEnumerator Start() { Debug.Log("Start method"); yield return StartCoroutine(TestCoroutine()); Debug.Log("Start method ends"); } private IEnumerator TestCoroutine() { Debug.Log("TestCoroutine"); int index = 0; while(index < 2) { Debug.Log("Here"); yield return null; Debug.Log("There"); index++; } } private void Update(){Debug.Log("Update");}
Start method
TestCoroutine
Here
Update
There
Here
Update
There
Start method ends
Update
Update
The calls for the end of the Start method is postponed until the coroutine is done. This requires the yield return for the coroutine and the Start method to return IEnumerator. Despite the fact it is possible and legit, Unity tends to not recommend that practice (without giving any reasons so it might be internal).
To Infinity and Beyond
That loop will never execute again if you make a call to stop the coroutines on the game object and it will not run again if the object is destroyed. It will also be paused if the script is disabled directly or by using SetActive(false) on the game object. This clearly shows the link between the MonoBehaviour and the coroutine.
void OnCollisionEnter(Collision col) { if(col.gameObject.CompareTag("Rocket")) { StartCoroutine(Explosion()); Destroy(this.gameObject); } }
The coroutine will run until hitting a yield, then the object is destroyed, the coroutine object is also destroyed in the process.
One way is to disable the renderer:
void OnCollisionEnter(Collision col) { if(col.gameObject.CompareTag("Rocket")){ StartCoroutine(Explosion(OnDestroyCallaback)); this.gameObject.GetComponent<Renderer>().enabled = false; this.gameObject.GetComponent<Collider>().enabled = false; } } private void OnDestroyCallback(){ Destroy(this.gameObject); } IEnumerator Explosion(Action action) { while(condition){ // do plenty of stuff yield return null; } if(action != null){ action(); } }
The renderer and collider are off, the explosion is happening and when over, the callback is called to destroy the object.
Second solution, you find a MonoBehaviour that is meant to live long enough, most likely a dedicated manager:
void OnCollisionEnter(Collision col) { if(col.gameObject.CompareTag("Rocket")) { Manager mb = FindObjectOfType<Manager>(); mb.StartCoroutine(Explosion()); Destroy(this.gameObject) } }
I Yield Sir
So I made a statement in the last section that you might be questioning. I said:
Unity processes coroutines every frame of the game for every object that has one or more running.
You may be thinking that it doesn’t in case like yield return new WaitForSeconds(1).
Unity does process that coroutine every frame, checking to see if the right amount of time has elapsed – it doesn’t process your code, but it does process the coroutine which is the wrapper it’s made around your script.
Here’s what you can yield:
- null – the coroutine executes the next time that it is eligible
- WaitForEndOfFrame – the coroutine executes on the frame, after all of the rendering and GUI is complete
- WaitForFixedUpdate – causes this coroutine to execute at the next physics step, after all physics is calculated
- WaitForSeconds – causes the coroutine not to execute for a given game time period
- WWW – waits for a web request to complete (resumes as if WaitForSeconds or null)
- Another coroutine – in which case the new coroutine will run to completion before the yielder is resumed
You can also issue the command yield break; which immediately stops the coroutine.
Because of WaitForEndOfFrame, coroutines can be used to get information from render textures when all cameras have completed rendering and the GUI has been displayed.
Using yield return new WaitForSeconds(x) will never resume if the Time.timeScale is set to 0.
Calling and stopping
public Coroutine StartCoroutine(IEnumerator routine); public Coroutine StartCoroutine(string methodName, object value = null); public void StopCoroutine(string methodName); public void StopCoroutine(IEnumerator routine); public void StopAllCoroutines(); IEnumerator MyCoroutine(){yield return null;} IEnumerator MyCoroutineOneParam(int i){yield return null;} IEnumerator MyCoroutineManyParams(int i, int j){yield return null;}
The second example makes use of a string parameter and one object parameter. This was useful before Unity allowed to stop a coroutine with IEnumerator. The only way was to call with a string and stop with a string. This process only allows one parameter (or then a bunch of them wrapped in an object).
Instead you can call the method directly inside if you plan on just running the coroutine, or use a IEnumerator reference:
//First case StartCoroutine(MyCoroutineOneParam(10)); //Second case IEnumerator coroutineObj = MyCoroutineManyParam(10,20); StartCoroutine(coroutineObj); StopCoroutine(coroutineObj);
The second case is a little more tricky, a call for the method is made and it returns a IEnumerator object. coroutineObj is a reference to that IEnumerator object, we pass it to StartCoroutine which actually starts the coroutine. With this solution, we are able to stop a single coroutine. Note that it is stopped, not paused, it won’t start again from where it left off.
Summary
- Coroutines are a really good way of making a sequence of operations happen over time or when some external process is completed
- Coroutines are not threads and are not asynchronous
- Nothing else is running when your coroutine is executing
- Your coroutine will resume when the conditions of your yield statement are met
- Coroutines are inactive when the script is disabled or the object is destroyed
- yield return new WaitForSeconds is dependent on game time which is affected by Time.timeScale
Practical Uses of Coroutines
First thing out of your mind, you should not think of using coroutines instead of Update, it is simply not their purpose despite doing a similar job, you won’t save resources nor memory. Keep them for temporary work, Update will do anything that should be done continuously.