Galaxy Shooter 2D — Coroutines #3
Learning how to use Coroutines in Unity as an alternative way to update functionality
When I started out coding in Unity, I used Update for everything: cooldowns, abilities, spawn rates… I just needed a few bools and floats to control that functionality.
But, it felt… cheap. Having every single feature updated all the time, checking conditions, or constantly updating values that don’t always need to be, didn’t feel right. There were many times where I really wished I could just execute or pause midway that code only when I wanted.
That was until I discovered Coroutines! It can detach functionality from the Update() method, executing code when I tell it to. And that’s today’s topic. I will show you how to I implemented Coroutines in a safe and effectively manner.
Results first, explanation later
Because Coroutines is an interesting topic to talk about, I want to explain them in-depth without giving it priority over my project. So, here’s the end result…
And the code…
Briefly explaining my code:
- I declared 4 variables:
- A GameObject variable to store and clone the Enemy prefab.
- A Vector3 variable to set the spawn point of the enemies.
- A WaitForSeconds variable which delays the execution of the Coroutine for 2 seconds.
- A Coroutine variable to store a reference of the actual Coroutine instance.
- At the start of the game, I call the StartEnemySpawn() method, which checks if there isn’t a SpawnEnemy Coroutine running, and starts and assigns the Coroutine to the variable.
- In the Coroutine, I purposefully made an infinite loop which waits for 2 seconds (WaitForSeconds variable), sets a spawn position (randomizing the X value), and Instantiating the Enemy.
- In case I want to stop that Coroutine, I have a method that checks if the Coroutine is running, and if it is, I stop the Coroutine and clear the reference in the “enemyRespawnCoroutine” variable. This last step is very important, as stopping the Coroutine doesn’t automatically clean it.
It’s really simple, and I don’t have to use the Update() method at all! Perhaps, it’s a bit intimidating if you’ve never seen a Coroutine before. But don’t worry, in the rest of this article, you’ll learn everything you need to know in detail.
How to create a Coroutine
To create coroutines, your script must include the library System.Collections. A Coroutine uses the interface IEnumerator, so all your methods that you want them to be a Coroutine must be of that type.
Because it’s not a void method (doesn’t return a value), it gives us an error because we are not returning anything. For Iterators, we use the keyword
yield to provide them context, and also pause execution until a condition is met. There are two forms to use this keyword:
yield return <expression>
This will return a value out of the expression given. We use this to halt temporarily the execution of the code until a result is returned. The most common expressions used are:
- yield return null: Pauses execution for one frame. This is used if you want to update your Coroutine like the Update method (called every frame).
- yield return new WaitForSeconds(<seconds>): Delays the execution of the code for the given amount of seconds. This comes in handy for behaviours like cooldowns!
- yield return StartCoroutine(<Your_Coroutine_Method>): Pauses execution until the recently started Coroutine ends. Nesting is possible, giving the opportunity to make more advanced functionality.
This will completely stop the Coroutine from running. This is automatically called when it successfully ran all the code inside the Coroutine. There is no need to write this, unless you want to force quit a Coroutine if a condition is met.
How to Start / Stop a Coroutine
Now that you now how a Coroutine operates, lets move on to starting and stopping one.
To start, we use the method StartCoroutine(), which takes a string or a method parameter.
- Using the string way, you gain the ability to stop all the coroutines started with the name provided, but, it’s harder to pass it parameters.
- Using the direct call to the method way, it’s easier to pass in parameters, but you lose the ability to stop it. (Don’t worry, there’s a way to stop it using this way).
Completely stops the Coroutine.
Only with what I have showed to you, the only way to stop a Coroutine would be by using the string approach. It searches for all coroutine with that name and stops them, so, if you only want to stop one Coroutine of that method, this way of stopping Coroutines is not for you.
However, there’s a way to stop a single instance of a Coroutine and a way to stop Coroutines started by the direct call to the method. By declaring a new Coroutine variable to store a reference to a Coroutine.
Using Coroutine variables as the best approach
In my opinion, using the direct call to the method and cache-ing the Coroutine reference is far cleaner and flexible than the string approach.
To properly use it, you will always want to assign the Coroutine reference by starting one in the same line. Like this:
And to stop it, just pass in the Coroutine variable to the StopCoroutine() method! So easy and clear to understand with this way of using them!
Dangers of Coroutines
If you’re not careful using loops, you could crash Unity (and your game)
Loops, as you probably know, it’s not something that runs between frames. If you have an endless loop, you must use something like
yield return null or
yield return new WaitForSeconds(<seconds>) to prevent your loop to crash the game.
Debugging is pain…
I’m not joking. For some reason, if you click the errors in your console, it will not redirect you to the line of code that is causing you the error. You have to manually scroll down, look for the line where the error occurs, and manually find that line and try to determine what the error is.
And not only that, if you messed up with loops and you need to test several times your game… have fun restarting Unity over and over again.
Another lengthy article (just a bit passionate about this topic)! If you’ve managed to read the whole article, I recommend you to scroll up again and review again my script. You should be able to completely understand it.
Thank you for reading this article! The next one will be about clearing a bit our Hierarchy. Spawning and seeing all these enemies in our Hierarchy can be a bit annoying. It will be a lot shorter, I promise. :)