Unity Coroutines – What Are They And How To Use Them

Table of Contents

Unity Coroutines – What Are They And How To Use Them

Reading Time: 7 minutes
Level: Beginner – Intermediate – Advanced
Version: Unity 2020.3.1 LTS

Help Others Learn Game Development

One of the most common behaviours in game development is the delayed behaviour.

A delay is used when you want to limit how many times your game character can shoot in a second, or when you are spawning new enemies, or when you are loading something in your game and so on.

In Unity, there are multiple ways how you can create a delayed behaviour.

One of the ways is using the Invoke function which we saw in the Rainy Knifes tutorial.

Another way to achieve this effect is using a coroutine function.

Before we start I want to mention that this tutorial is suited for beginners, intermediate and advanced Unity users.

However, if you are a beginner, I expect you to know your way around Unity.

I expect that you know how to create a new project, how to create a class, how to attach a class to a game object, I expect you to know the basics of programming with C# in Unity, how to print to the Unity console and other basic beginner stuff.

If you don’t know these things, then you should check out my C# tutorial series starting with variables.

After that go through my Rainy Knifes tutorial.

How To Declare A Coroutine

I have already created a new 2D project and I created a script called CoroutinesTester where we will write all the code for this tutorial, so make sure you do the same.

We already know what is a coroutine, as I already mentioned a coroutine is a function that allows us to delay the execution of our code.

So how do we declare a coroutine function?

The signature for a coroutine function goes like this:

				
					IEnumerator CoroutineName()
    {
    }
				
			
As we can see, creating a coroutine is like creating a function that returns a value, in this case that value is IEnumerator, then we can name the coroutine however we want and the we use the brackets and curly brackets same as with a normal function.
 
Of course, we will give our coroutines meaningful names and we will follow the same naming convention that we normally follow for any function we create.
 
When you create a coroutine like we did above, you will see a red line under your coroutine name indicating that we have an error in our code:
 
Img 1
This is because we stated that the coroutine returns an IEnumerator but we didn’t provide any in the function.
 
We can easily fix this by calling the return statement:
 
				
					IEnumerator CoroutineName()
    {
        yield return new WaitForSeconds(2f);
    }
				
			
So what is happening here?
 
Inside the coroutine we don’t use the return statement only, we also need to add yield before it and then we call new WaitForSeconds() for example.
 
I said for example, because there are other wait objects we can call to stop the time in a coroutine:
 
Img 2
In our example above we are calling WaitForSeconds() which will delay the execution for the provided amount of time.
 
In the code we wrote 2f, which means that WaitForSeconds() will wait 2 seconds before the execution of the coroutine continues.
 

Different Wait Objects We Can Call In A Coroutine

As we saw from the image above there are multiple wait objects we can call. I am going to list them here and explain what each means.
 
WaitForEndOfFrame – waits until the end of the frame in the Update function
 
WaitForFixedUpdate – waits until the next frame of the FixedUpdate function
 
WaitForSeconds – suspends the coroutine execution for the given amount of seconds using scaled time
 

WaitForSecondsRealTime – suspends the coroutine execution for the given amount of seconds using unscaled time

WaitUntil – suspends the coroutine execution until the supplied delegate evaluates to true
 
WaitWhile – suspends the coroutine execution until the supplied delegate evaluates to false
 
The first and the second wait object are connected to Update and FixedUpdate functions and they wait for their frames, one for the end of the frame and the other for the beginning of the next frame to continue the execution of the coroutine.
 
The WaitForSeconds and WaitForSecondsRealtime suspend the coroutine execution for the provided amount of seconds, but one uses scaled time which means if we were to use Time.timeScale = 0 to stop the time, the WaitForSeconds will also stop, whereas the WaitForSecondsRealtime is not affected by time scale and it will continue to execute even if time scale value is 0.
 
The last two wait objects take a delegate and wait for it to return true or false to continue the execution.
 
In our development we will mostly use WaitForSeconds and WaitForSecondsRealtime to create the delayed behaviour using coroutines.
 

How To Call A Coroutine

First I am going to create a new coroutine that will print to the console after the delay time:
 
				
					IEnumerator PrintToConsole()
    {
        yield return new WaitForSeconds(2f);
        Debug.Log("Printed after wait time");
    }
				
			

Since a coroutine is also a function, one might think that you can call it like a regular function:

				
					private void Start()
    {
        PrintToConsole();
    }
				
			

But this will not work. We can say that a coroutine is a special kind of function and because of that we need to call it in a special way.

To do that, we use the StartCoroutine function:

				
					private void Start()
    {
        StartCoroutine(PrintToConsole());
    }
				
			

Inside the StartCoroutine function we provide the coroutine as a parameter and this is how the coroutine is called.

Attach the CoroutinesTester script on an empty game object in the Hierarchy tab and run the game.

You will see that Debug.Log will print to the console the text we provided after 2 seconds of waiting:


Having More Control Over Coroutines

What we learned so far are the things that you will see in most tutorials, books and classes, and this is where the concept of coroutines stops for all of them.
 
However, there are more untouched topics in regards to coroutines that we will start to cover from here on.
 
Let’s say we create a coroutine that will spawn enemies in our game, and we get to the point where we finish the level. When that happens we don’t want to spawn new enemies in the game.
 
How can we stop the coroutine from executing and spawning new enemies?
 
To stop a coroutine that is already started, we need to call the coroutine by passing the name of the coroutine as a string and not passing the coroutine as the parameter.
 
It goes like this:
 
				
					private void Start()
    {
        StartCoroutine("PrintToConsole");
    }
				
			
If you were to test this line of code, you will see that Debug.Log will print to the console after the wait time, same as like in the previous example.

But by calling a coroutine using this approach, we can use the StopCoroutine function to stop the execution of the coroutine:
 
				
					private void Start()
    {
        StopCoroutine("PrintToConsole");
    }
				
			
When the StopCoroutine function executes, the coroutine will stop executing no matter at which point of its execution it is at.
 
One important thing to emphasize here is that you need to use the same name of the coroutine when you want to start it and when you want to stop it.
 
If you start a coroutine by calling:
 
				
					private void Start()
    {
        StartCoroutine("PrintToConsole");
    }
				
			
And you try to stop the execution of that coroutine by calling:
 
				
					private void Start()
    {
        StopCoroutine("PrintToconsole");
    }
				
			

It will not work because when you run the coroutine the c for the console was capitalized, but when you tried to stop the coroutine the c for the console was lower case, so make sure that you pay attention to the correct capitalization of the coroutine name.

To test this out, I am going to create another function that will stop the coroutine execution:

				
					private void Start()
    {
        StartCoroutine("PrintToConsole");

        Invoke("StopExecution", 3f);
    }

    void StopExecution()
    {
        StopCoroutine("PrintToConsole");
    }

    IEnumerator PrintToConsole()
    {
        yield return new WaitForSeconds(0.5f);
        Debug.Log("Printed after wait time");

        StartCoroutine("PrintToConsole");
    }
				
			
One thing that you will notice in the code above is that I am calling StartCoroutine inside the PrintToConsole coroutine, and this is totally legit and this way you can make the coroutine call it self over and over again.
 
I am using Invoke function to call the StopExecution function which will stop the execution of the PrintToConsole coroutine:
 
As you can see, the PrintToConsole coroutine is called every 0.5 seconds, but after the Invoke function called the StopExecution function after 3 seconds, the PrintToConsole coroutine stopped executing.
 

WaitForSeconds And WaitForSecondsRealtime

When I listed all the wait object we can use to stop a coroutine, I mentioned that we will use WaitForSeconds or WaitForSecondseRealtime for the most part.
 
I also mentioned what is the difference between the two which is WaitForSeconds is dependent on time scale while WaitForSecondsRealtime is not dependent on time scale.
 
Let us first demonstrate how WaitForSecondsRealtime works.
 
I am going to rewrite our coroutine so that it uses WaitForSecondsRealtime:
 
				
					IEnumerator PrintToConsole()
    {
        yield return new WaitForSecondsRealtime(2f);
        Debug.Log("Printed after wait time");
    }
				
			
If you call this coroutine inside the Start function and run the game, you will see that Debug.Log will print to the console after 2 seconds. Basically it has the same behavior as WaitForSeconds.
 
Let’s perform another test:
 
				
					private void Start()
    {
        StartCoroutine(PrintToConsole());

        Invoke("StopExecution", 3);
    }

    void StopExecution()
    {
        Time.timeScale = 0f;
    }

    IEnumerator PrintToConsole()
    {
        yield return new WaitForSeconds(0.5f);
        Debug.Log("Printed after wait time");

        StartCoroutine(PrintToConsole());
    }
				
			
As you can see, I have a similar set up like we did when we tested the StopCoroutine function.
 
On line 10, inside the StopExecution function I am setting the time scale value to 0.
 
For those of you who don’t know what time scale is, it is the scale by which time passes.
 
Meaning, time scale controls if the game runs or not. When the value of time scale is 1, then the game runs, if the value is 0 the game stops.
 
This way is commonly used to create the pause effect for games in Unity.
 
Let’s run this game and see what happens in the console:
 

As you can see, every 0.5 seconds we see a print in the console, but after 3 seconds when the Invoke function calls StopExecution function where time scale is set at 0 the printing stops.

As long as time scale stays at 0 the coroutine will not run, when we put the value of time scale back at 1, then the coroutine will continue running.

Now let’s see what happens when we use the same code set up, except in the PrintToConsole we are going to use WaitForSecondsRealtime instead of WaitForSeconds:

				
					IEnumerator PrintToConsole()
    {
        yield return new WaitForSecondsRealtime(0.5f);
        Debug.Log("Printed after wait time");

        StartCoroutine(PrintToConsole());
    }
				
			

I am also going to add a Debug.Log inside the StopExecution function so that we know when it is called:

				
					void StopExecution()
    {
        Time.timeScale = 0f;
        Debug.Log("Time scale is 0");
    }
				
			
Let’s see what happens in the console when we run the game now:
 

The printing continues even after 3 seconds when the Invoke function is called to run the StopExecution function which we saw by the print in the console that informed us that time scale is now 0, which means that WaitForSecondsRealtime is not affected by time scale.

WaitUntil And WaitWhile

WaitUntil and WaitWhile wait for the supplied delegates to return true and false to continue the execution of the coroutine.
 
First I need to create a countdown variable and a function that will be used as a delegate.
 
Below the class declaration create a new int variable:
 
				
					int countdown = 3;
				
			
Next, create the function that will be used as a delegate:
 
				
					bool AllowToPrint()
    {
        if (countdown <= 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
				
			

I am also going to modify our coroutine so that it uses WaitUntil:

				
					IEnumerator PrintToConsole()
    {
        yield return new WaitUntil(AllowToPrint);
        Debug.Log("Printed after waiting for the delegate");
    }
				
			

So when the value of the countdown variable is less than or equal to 0, the AllowToPrint function will return true and this when the WaitUntil will trigger and allow the coroutine to continue the execution.

For that I am going to create a function that will subtract 1 from the countdown variable very time it is called:

				
					void SubtractCountdown()
    {
        countdown--;
    }
				
			

And in the start function I am going to start the coroutine and use InvokeRepeating to call the SubtractCountdown to subtract 1 from the countdown variable after every second:

				
					private void Start()
    {
        StartCoroutine(PrintToConsole());
        InvokeRepeating("SubtractCountdown", 0f, 1f);
    }
				
			

The InvokeRepeating function will call the SubtractCountdown function first time after 0 seconds, which is the first number parameter provided.

Then it will call the SubtractCountdown function repeatedly after 1 second which is the second number parameter that is provided.

Before we test this out, because we are using InvokeRepeating, we need to add one more line of code in the AllowToPrint function to stop the InvokeRepeating function after the countdown value reaches 0:

				
					bool AllowToPrint()
    {
        if (countdown <= 0)
        {
            CancelInvoke("SubtractCountdown");
            return true;
        }
        else
        {
            return false;
        }
    }
				
			
When we run the game we will see that when the countdown value gets to 0, the coroutine will execute:
 

For the WaitWhile, we can modify the AllowToPrint function to return false when the countdown value reaches 0:

				
					bool AllowToPrint()
    {
        if (countdown <= 0)
        {
            CancelInvoke("SubtractCountdown");
            return false;
        }
        else
        {
            return true;
        }
    }
				
			
And also need to modify the coroutine to use the WaitWhile:
 
				
					IEnumerator PrintToConsole()
    {
        yield return new WaitWhile(AllowToPrint);
        Debug.Log("Printed after waiting for the delegate");
    }
				
			
Everything else can stay the same. When we run the game we will see the same result:
 


Coroutines Can Take Parameters

Same as a normal function, coroutines can also take parameters that can be used inside the coroutine.
 
Let’s rewrite our PrintToConsole coroutine:
 
				
					 IEnumerator PrintToConsole(string msg, float waitTime)
    {
        yield return new WaitForSeconds(waitTime);
        Debug.Log(msg);
    }
				
			

Now call the coroutine in the Start function:

				
					private void Start()
    {
        StartCoroutine(PrintToConsole("Coroutine parameter printed to console", 2f));
    }
				
			

When we run the game this is what we will see in the console:

The string parameter we passed to the coroutine is printed in the console using the float parameter we passed as the wait time.
 
Of course, you can have any type of parameter for the coroutine as well as many parameters as you wish, you are not limited to 2 parameters, you can have 3, 5, 10 and so on.
 

Leave a Comment