In this post, we will be looking at coroutimes, and see how they can be used in Unity. This concept is very important, especially when you want to include tasks that may slow-down your overall game, such as loading for a file and waiting for this to be completed.
In a nutshell: coroutines are a bit like a soccer team where the ball is passed to a player; this player takes the ball and starts to run, however, s/he needs information from the team doctor to know whether s/he can go ahead and what s/he can do to heal a recent injury; so s/he just passes the ball to another team mate and freezes (yield) until s/he receives instructions from the doctor. When s/he receives this instruction, the ball his passed to him/her again and s/he resumes to play. So in this case the players share the ball, but only one player has the ball at one particular time. So coroutines are a way to collaboratively run a program with only one function running at a time. If you have used threads, coroutines and threads differ in that threads run in parallel whereas coroutines work collaboratively to freeze one function and give it the focus again when criteria have been fulfilled (i.e., only one coroutine running at any given time). Coroutines are usually referred as concurrency as they pass control to each-other.
A coroutine is interesting in that it can give the control back to the main program and then resume where it stopped. So you can call the coroutine, and have it to complete some work, give the control back to the main program; so because you can keep track of where the routine paused, it can be useful to schedule events so that the coroutine completes tasks only in specific circumstances.
By default, a coroutine method is declared with the keyword IEnumerator beforehand; it also usually includes a yield statement marking where the coroutine should pause and resume. A coroutine is also usually called using the keyword StartCoroutine, as illustrated in the next code snippet.
void Update () { StartCoroutine (myCoRutine ()); } IEnumerator myCoRutine () { yield return 0; }
In the previous code;
- We call the method myCoRoutine from the Update
- The method myCoRoutine that will be used as a coroutine, is declared with the keyword IEnumerator; this makes sure that this method can be paused and resumed as a coroutine.
- The coroutine returns directly to the part of the program that called it in the first place.
The only issue with this code is that the coroutine will be called every frame (which defeats the purpose of the coroutine); if instead, we would prefer it to be called every 5 seconds, we could manage to freeze it for 5 seconds using the method WaitForSeconds; this method, as illustrated in the next code snippet, can be used to pause a coroutine for a specific amount of time.
float time; void Start () { time = 0; } void Update () { time += Time.deltaTime; StartCoroutine (myCoRoutine ()); } IEnumerator myCoRoutine () { while (time >5) { time = 0; print ("Hello time:"+ (int)time); yield return (new WaitForSeconds(5)); print ("Just resuming after 5 seconds"); } }
In the previous code:
- We create a variable. called time, that will be used to monitor the time.
- This variable is increased every seconds.
- As previously, we call the coroutine myCoRoutine.
- In this coroutine, we create a loop that will be triggered whenever the time goes over 5 seconds.
- As we enter the loop we reset the time to 0.
- We also print a message that includes the time and then suspend this coroutine for 5 seconds. So the next time the routine resumes (after 5 seconds), it should print the message “Resuming after 5 seconds which is located just after the yield statement.
- The time variable will be increased by one every seconds in the meantime; while the coroutine is suspended, the Update function is still executed, so the time is updated every seconds.
- So after 5 seconds, the coroutine is resumed, and the value of the variable time is also more than 5; this means that we will enter the loop and resume just after the yield
- Because the time is reset to 0 every time we enter this loop, and because we are using a while loop, the coroutine will indefinitely be called every 5 seconds.
Note that the condition for the coroutine to pause is that 5 seconds have elapsed; this is done through the statement new WaitForSeconds(5); this being said, it is also possible to employ other conditions to specify when the coroutine should resume, such as WaitUntil or WaitWhile; for example, we could use the following code instead .
yield return (new WaitUntil (()=> time>5));
In the previous code, we will wait to resume the coroutine until time is greater than 5.
So this example shows how you can manage to schedule events and actions that should only be executed at specific times; these actions are embedded in coroutines that can be frozen overtime; to use the analogy of the soccer team: every 5 seconds we pass the ball to the player X (the coroutine) who will perform actions with it (e.g., dribble) and will give the ball back to other member of the team after this time has elapsed.
Note that we have used a loop in the code; otherwise, we could not indefinitely call the coroutine.
So coroutines are very useful, because they make it possible to run code asynchronously and can be used in several occasions, including:
- Scheduling actions based on time (as we have seen above).
- Loading elements in your game at specific times, to ensure that the game will still be responsive.
- Suspend execution until data has arrived (we will have a look at this in the next section).
- Schedule the actions of Non-Player Characters over time.
- Use to customize any actions performed in the Update function (including dealing with finite state machines).
When dealing with loading data over the internet, using coroutines is often useful; for example, the following code could be used to load the content of a php page.
using UnityEngine; using System.Collections; public class AccessDB : MonoBehaviour { string url = "http://localhost:8888/updateScore.php"; // Use this for initialization IEnumerator Start() { WWW www = new WWW(url); yield return www; string result = www.text; print("data received"+result); } void Update () { } }
In the previous code:
- We declare the class AccessDB.
- We then create a string called url that stores the address of the PHP page that we will access.
- We declare a function called Start using the keyword IEnumerator This keyword is used to specify that the function Start has become a coroutine, which means that it now has the ability to be paused until a condition has been fulfilled.
In our case, it is necessary to declare this function as a coroutine because the code that we use to gather information from the PHP script will need to send a request to the server and then wait for the answer. However, we don’t want the whole program to stop while this data is on its way (e.g., we still need to update the screen and perform other important tasks). So in that sense, this function does not act like a usual function, in that it doesn’t just perform actions and return to where it was called from; instead, because part of its tasks is to gather (and possibly wait for) information from the server, which may involve delays, as a coroutine, this function will fetch for the server’s data and pause itself until the data has been received. Meanwhile, other functions (such as the Update function, for example) will be able to run in the meantime; then, when the data is received from the server, the Start function is called again just after the point where it had been paused.