Unity Coroutine isn’t complicated

Many functions may seem complex at the beginning but turn out to be simple once you understand the concept. Unity coroutine is one such thing. A coroutine is a function that allows pausing its execution and resuming from the same point after a condition is met. You can start a coroutine and wait for its results before your code moves on to the next line. Or you could let the coroutine handle its business on the side while your code moves on to execute the rest of its functions.

Unity Coroutine

Unity coroutine gives you the power to decide how the execution of code is to be done. You can execute the coroutine in a way to optimize our game performance. In this post, we will see how to start and stop a coroutine along with its advantages and disadvantages.

So, what does this mean exactly? Why would we use a coroutine? Well, there are many situations where coroutines can come in handy. We will go through them one by one.

start coroutine method

As the name suggests the Start coroutine method is used to start a coroutine. This method takes a function as input. Let’s start with a very simple and common example to understand this.

Let’s say you want to spawn a cube in a scene 5 seconds after the game starts. You could implement a Unity timer function in the Update and then check how much time has passed, but there is a much simpler way of achieving this with the Unity coroutine.

public class ExampleScript : MonoBehaviour
{
   public GameObject objectToSpawn;
   private void Start()
   {
       StartCoroutine(SpawnBoxAfterSeconds(5f));
   }

   private IEnumerator SpawnBoxAfterSeconds(float seconds)
   {
       yield return new WaitForSeconds(seconds);
       Instantiate(objectToSpawn);
       Debug.Log("Spawned " + objectToSpawn.name + " after " + seconds + "              seconds!");
   }
}

There might be a few things that may be unfamiliar to you in the code above. Let’s run through them.

IEnumerator

Firstly, the function SpawnBoxAfterSeconds created with 1 parameter. Notice that the return type is of IEnumerator. IEnumerator allows to stop the process at a specific spot in code and return a result. It then continues to the point at which it stopped. IEnumerator is of enum datatype. An enum only takes predefined constants as input. For example, if you take the compass the enum will take only east, west, north, and south.

The first line on the code states: yield return new WaitForSeconds(seconds);

As you might have guessed, this line allows us to wait for 5 seconds. After 5 seconds, it will execute the code that follows it. In this case, it will instantiate an object then print a debug log.

Yield

The keyword “yield” is what actually tells Unity coroutine to pause the script and continue on the next frame. It will keep doing this for 5 seconds, and then the code following it will execute.

A Unity coroutine is like a normal function, but it is called differently. Instead of calling it like any other function “SpawnBoxAfterSeconds(5f)” we need to specify that the coroutine needs to start. So the code will be “StartCoroutine(SpawnBoxAfterSeconds(5f))”

This is true for all coroutines. Failing to do this will not throw an error, but the Unity coroutine function will not work at all.

using System.Collections;
using UnityEngine;

public class ExampleScript : MonoBehaviour
{
   public GameObject objectToSpawn;
   private void Start()
   {
       StartCoroutine("SpawnBoxAfterSeconds");
   }

   private IEnumerator SpawnBoxAfterSeconds()
   {
       yield return new WaitForSeconds(5f);
       Instantiate(objectToSpawn);
       Debug.Log("Spawned " + objectToSpawn.name + " after " + "5 seconds!");
   }
}

This is also another way of calling coroutines. You can pass the name of the function as a parameter in string format. However, you cannot pass a parameter for the function. It’s better to use the earlier examples in all scenarios. This way you are not prone to spelling errors, and you have the option to pass parameters if you want to later down the line. The way you start the coroutine also decides how you are going to stop it. We will discuss more on this in the StopCoroutine section.

Stopping a Unity coroutine

Now that we know how to start a coroutine, let’s go through how to stop it. It’s quite simple. There are 2 ways to stop a coroutine. The most common way is to use StopCoroutine(“SpawnBoxAfterSeconds”);

You do not need to specify the parameter. It will simply stop the Unity coroutine that has that function name. This method will only work if you also started the coroutine by passing the function name as a string like in the 2nd example in Starting a coroutine.

Now there is a problem here. What if I called that coroutine more than one time. What will happen if I stop it as I did in the above example? This will stop ALL the coroutines with that function name. So if I did this:

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

      StopCoroutine("SpawnBoxAfterSeconds");
   }

This would stop all 3 Unity coroutines. This may or may not be what you intend to do, so use it with this point in mind.

As I mentioned, stopping a coroutine by passing a string parameter only works if you start it the same way. How do we stop a coroutine if it was called like in the 1st example in Starting a coroutine?

Well, you would need to store the coroutine into a variable when you start it. That way you can stop the coroutine by passing that variable instead. Here’s an example:

public class ExampleScript : MonoBehaviour
{
   public GameObject objectToSpawn;
   private Coroutine spawnBoxCoroutine;
   private void Start()
   {
       spawnBoxCoroutine = StartCoroutine(SpawnBoxAfterSeconds(5f));
      
       StopCoroutine(spawnBoxCoroutine);
   }

   private IEnumerator SpawnBoxAfterSeconds(float seconds)
   {
       yield return new WaitForSeconds(seconds);
       Instantiate(objectToSpawn);
       Debug.Log("Spawned " + objectToSpawn.name + " after " + seconds + " seconds!");
   }
}

As you can see in the example above, I created a variable of type Unity Coroutine. When I call StartCoroutine, I store it into that object. This way I have a reference to it. In the next line, I call StopCoroutine(); and instead of passing a string with the name of the function. I pass the Coroutine object.

Note: Since we are creating a reference to the object, you can also do this even if you start the coroutine like this spawnBoxCoroutine = StartCoroutine(“SpawnBoxAfterSeconds”);

The final way of stopping a Unity coroutine is by passing the IEnumerator variables inside StopCoroutine(). Here’s an example.

public class ExampleScript : MonoBehaviour
{
   public GameObject objectToSpawn;
   private IEnumerator SpawnBoxIEnumerator;
   private void Start()
   {
       SpawnBoxIEnumerator = SpawnBoxAfterSeconds(3f);
       StartCoroutine(SpawnBoxIEnumerator);
      
       StopCoroutine(SpawnBoxIEnumerator);
   }

   private IEnumerator SpawnBoxAfterSeconds(float seconds)
   {
       yield return new WaitForSeconds(seconds);
       Instantiate(objectToSpawn);
       Debug.Log("Spawned " + objectToSpawn.name + " after " + seconds + " seconds!");
   }
}

Wait For Seconds Method

We have a basic grasp of how coroutines work and how to call them. Now I’m going to show an example of waiting for a few seconds because it is used very often by developers. This can be used to replace the timer function and is a more efficient way to make a timer.

public class ExampleScript : MonoBehaviour
{
   public GameObject objectToSpawn;
   private IEnumerator SpawnBoxIEnumerator;

   private void Start()
   {
       StartCoroutine(SpawnBoxAfterSeconds(3f));
       Debug.Log("Complete!");
   }

   private IEnumerator SpawnBoxAfterSeconds(float seconds)
   {
       yield return new WaitForSeconds(seconds);
       Instantiate(objectToSpawn);
       yield return new WaitForSeconds(2f);
       Debug.Log("Spawned " + objectToSpawn.name + " after " + Time.time + " seconds!");
   }

Console log

As you can see, inside start, we start the coroutine and then print “Complete!”. When the coroutine started it waited for 3 seconds (because that’s the amount we passed through the parameter), and then instantiated a GameObject. Then it waited for 2 additional seconds and printed another message stating that it spawned Cube after 5 seconds. It says 5 seconds because that is the amount of time that passed since the game started (3 + 2 seconds). I got this time by using Time.time, which is a variable that returns the amount of time since the game started.
 
We have another problem! Inside the start function, we printed “Complete!”, but the coroutine is not complete yet. Let’s fix that in this coming section.

Waiting for Coroutines to finish

In order to wait for a coroutine to finish processing before we can move on, we need to yield from the place that we are calling the coroutine. Here’s the example:

private IEnumerator Start()
{
   yield return StartCoroutine(SpawnBoxAfterSeconds(3f));
   Debug.Log("Complete!");
}

private IEnumerator SpawnBoxAfterSeconds(float seconds)
{
   yield return new WaitForSeconds(seconds);
   Instantiate(objectToSpawn);
   yield return new WaitForSeconds(2f);
   Debug.Log("Spawned " + objectToSpawn.name + " after " + Time.time + " seconds!");
}

I made the start function of type IEnumerator and added the keywords yield return before I started the coroutine. Yield as we mentioned, is what tells unity to move on to the next frame. So until the SpawnBoxAfterSeconds function hasn’t completed, we do not move on to the next line. Note that this could cause the game to get stuck in an infinite loop if not written correctly. If the coroutine I wrote for some reason never completes because of a condition that has a logical error, the game will get stuck, so please keep that in mind!

Below is the result of the above code. As you can see, “Complete!” gets called right after the debug message is called.

Advantages and Uses of Unity Coroutine

Coroutines are mainly used when necessary rather than for convenience. It is easy to use, and it’s quite handy in many situations. However, the sad truth is that coroutines are expensive when overused or when not tracked. Make sure that your coroutines always end. And if they do not work in a way that they end when a condition is satisfied (maybe you want to constantly calculate something all the time), then stop the coroutines manually through code yourself. Clean up is very important when using a lot of coroutines.

Although you can use hundreds of them, they do require a lot of performance at that point.

Advantages

  • The advantages of coroutines are that they work wonders in smaller games. You wouldn’t need to worry about the performance overhead, and you can use them as you please. Don’t get me wrong, you can use them even in large-scale games, but they need to be used carefully and properly.
  • They are convenient when you want to wait (as shown several times above), you can wait for a few seconds, you can wait for the end of the frame, and you can even bypass Unity time. This means that if your Time.timescale is set to 0 when your game is paused, or is part of your game’s mechanics, you can use yield return “yield return WaitForSecondsRealtime(2f)
  • Another advantage is that you can run a separate block of code on the side while your script goes through all its lines of code. What I mean by this is that when you call a coroutine, a piece of functionality can run without your main code having to stop and wait for the process to finish. An example was made in the Wait for seconds example, where the print happened even though the coroutine hadn’t finished its process yet. Which can come in handy.

Disadvantages

  • A disadvantage would be that coroutines return IEnumerator, so you cannot return a float or GameObject, for example. Although it is possible, it would require further implementation that is not provided by Unity by default.
  • As mentioned above, they can be expensive if used too much, because they also create garbage and are not completely running on a separate thread in the backend.
  • Another disadvantage arises if Coroutines are used for the wrong reason, or implemented wrong. For example, if you have some code running that depends on a variable being set inside a coroutine, you may get a null result because the coroutine didn’t set the variable in time.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.