Only Continue Code After Coroutine Finishes
If you're new to Unity, it's possible that you've written a lot of your code inside the Update function.
This may mean that you're checking boolean conditions to manage states and using if conditions to call functions and to deploy different blocks of code at different times.
Which is fine, especially when you're just getting started with scripting in Unity.
However…
If you've ever tried to delay a function or have it wait for another action to finish, or if you've tried to pause it halfway through or you want to queue up a series of actions to take place one after another, you may have found this difficult to do inside Update or using a regular function.
But don't worry, because that's where Coroutines come in.
Coroutines explained (video)
For an overview of how to use coroutines in Unity try my video, or continue to the full article below.
What are Coroutines in Unity?
Code that's called inside of Update usually takes place all at once during the current frame.
Coroutines, on the other hand, allow you to execute game logic over a number of frames.
Put simply, this allows you to pause a function and tell it to wait for a condition or action to occur before continuing. This allows you to split up its functionality into a number of steps that can be executed in order.
This is fantastic for carrying out actions that would otherwise be impossible, inefficient or just plain difficult to do inside of Update alone.
And while it is possible to achieve many of the functions that Coroutines are designed for using other techniques, Coroutines can be extremely convenient, easy to use and very efficient.
So long as you use them the right way.
So, in this post, you'll learn how to write a Coroutine, how to start it, stop it and pause it, as well as some best practices for when you should, and shouldn't, be using them. So that you can be confident that you're using Coroutines the right way in your project.
What you'll find on this page:
- Why use a Coroutine?
- When to use a Coroutine
- How to write a Coroutine in Unity
- How to pause a Coroutine (Yield)
- How to start a Coroutine
- How to end a Coroutine
- Coroutine best practices
- Coroutine vs Invoke vs Async
Why use a Coroutine?
Coroutines are ideal for setting up game logic that needs to takes place over time (multiple frames instead of one).
This may not sound like much at first, but this can be incredibly helpful.
To get an idea of just how useful Coroutines can be, consider the following example.
Let's say I have a tank, and when I click on the map I want that tank to turn to face where I clicked, move towards the position and, when it gets there, wait 1 second before firing.
Like this….
Basically I'm giving the tank a To-Do list…
- Turn to a specific angle.
- Move to a specific point.
- Wait for 1 second, then fire.
Easy right?
While the logic to achieve this seems simple, if I wanted to do this in Update, it can quickly become confusing and difficult to manage.
To prevent all of the To-Do list items from happening at the same time (which is how Update normally works) I have to keep checking to see if the tank is supposed to be turning, moving or firing at any given moment.
In scripting, using Update based functions, it would look something like this:
// Please don't do this bool tankMoving; bool facingRightWay; bool tankInPosition; float timer; void Update() { if(Input.GetMouseButtonDown(0)) { tankMoving = true; } if (tankMoving) { MoveTank(); } } void MoveTank() { if (facingRightWay == false) { if (angleIsWrong) { TurnTank(); } else if (angleIsCorrect) { facingRightWay = true; } } if (facingRightWay && tankInPosition == false) { if (positionIsWrong) { MoveToPosition(); } else if (positionIsCorrect) { tankInPosition = true; } } if (facingRightWay && tankInPosition && timer < 1) { timer += Time.deltaTime; } else if (facingRightWay && tankInPosition && timer > 1) { FireTank(); facingRightWay = false; tankInPosition = false; tankMoving = false; timer = 0; } }
As you can see, it's a lot more complicated than it needs to be.
But that's not even the biggest issue with this method…
While researching for this article, I did manage to get this to work using only Update based code, however, it wasn't easy at all.
For example, my first attempt had the tank infinitely spinning, firing bullets immediately in random directions whenever I clicked.
Not what I wanted.
This happened because my fire function wasn't checking to see if the other steps had already taken place. I found it difficult to keep track of which conditions needed to be true, I made a mistake and so everything was taking place out of turn.
Which is the real problem when trying to execute this kind of 'staged' logic in an Update loop.
Apart from being inefficient, it's counter-intuitive and difficult to manage.
But it doesn't need to be.
Using a Coroutine makes it much easier.
Here's the same logic again, this time inside a Coroutine:
void Update() { if(Input.GetMouseButtonDown(0)) { StartCoroutine(MoveTank()); } } IEnumerator MoveTank() { while(facingWrongWay) { TurnTank(); yield return null; } while (notInPosition) { MoveToPosition(); yield return null; } yield return new WaitForSeconds(1); Fire(); }
This time, the code works more like a To-Do list with each action being executed after the last has completed.
Unity processes each of the While Loops until their condition is no longer true, before moving on to the next.
This isn't actually any different from how Unity executes a regular function except that, now, the logic is being carried out over multiple frames instead of one frame.
This works because of the yield keyword, which tells Unity to stop what it's doing and continue again on the next frame.
The end result is a sequence of events that's easier to write and easier to manage.
It's also more efficient, as Unity no longer has to check for conditions that aren't relevant yet.
And, once all of the actions are completed, the Coroutine ends.
When to use a Coroutine in Unity
It's worth considering using a Coroutine whenever you want to create an action that needs to pause, perform a series of steps in sequence or if you want to run a task that you know will take longer than a single frame.
Some examples include:
- Moving an object to a position.
- Giving an object a list of tasks to carry out (like in the To-Do list example earlier).
- Fading visuals or audio in and out.
- Waiting for resources to load.
These are the kind of tasks that Coroutines are well suited for.
But how do you actually write one?
How to write a coroutine
A Coroutine is laid out in a similar way to a regular function, with a couple of key differences.
First, the return type of a Coroutine needs to be IEnumerator, like this:
IEnumerator MyCoroutine() { // Code goes here! }
If you're not familiar with IEnumerator, don't worry.
All you really need to know about it right now is that this is how Unity splits the function's execution across multiple frames.
Just like normal functions, you can pass parameters into a Coroutine.
IEnumerator MyCoroutine(int number) { number++; // Code goes here. }
Variables that you initialise inside the Coroutine will hold their value for its duration.
Also, unlike regular functions, Coroutines allow you to pause the code while it's executing using a yield statement.
How to pause a Coroutine in Unity (Yield)
There are a few different types of yield statement that will pause a Coroutine and which one you use depends on how long you want to pause the method for.
In all cases, however, you'll need to start with the keywords yield return at the point you want the function to be interrupted.
Why these keywords?
Yield indicates that the method is an iterator and that it's going to execute over more than one frame, while return, like in a regular function, terminates execution at that point and passes control back to the calling method.
The difference is that, with a Coroutine, Unity knows to continue the method where it left off.
What follows yield return will specify how long Unity will wait before continuing.
So, what are the options?
Yield Return Null (wait until the next frame)
Yield return null instructs Unity to wait until the next frame before continuing.
Combining yield return null with a while Loop creates mini Update Loops. Like this.
IEnumerator MyCoroutine() { int i = 0; while (i < 10) { // Count to Ten i++; yield return null; } while (i > 0) { // Count back to Zero i--; yield return null; } // All done! }
Unity will work through the first loop, counting one number every frame and then the second, counting back down by one each frame, until the end of the code block.
Without yield return null, all of the code would be executed at once, just like a regular function.
Wait For Seconds (wait for a period of time)
Wait For Seconds or Wait For Seconds Real Time (which uses unscaled time) allows you to specify an exact amount of time to wait. It can only be used inside a Coroutine (i.e. it doesn't work in Update).
Just like before, you'll need to use Wait for Seconds with the yield return statement and, in this case, usually the new keyword for it to work.
In scripting it looks like this:
IEnumerator WaitFiveSeconds() { print("Start waiting"); yield return new WaitForSeconds(5); print("5 seconds has passed"); }
Using Wait for Seconds like this works best for one-off waits.
For a repeating delay, it's slightly better performance to cache the Wait for Seconds object first.
This also means you won't need the new keyword. Like this:
WaitForSeconds delay = new WaitForSeconds(1); Coroutine coroutine; void Start() { StartCoroutine("MyCoroutine"); } IEnumerator MyCoroutine() { int i= 100; while (i>0) { // Do something 100 times i--; yield return delay; } // All Done! }
Wait for Seconds Real Time performs the exact same function, but uses unscaled time instead. Which means it will still work even if you alter the time scale, like when pausing the game.
Like this:
IEnumerator WaitFiveSeconds() { // Pause the game Time.timeScale = 0; yield return new WaitForSecondsRealtime(5); print("You can't stop me"); }
Yield Return Wait Until / Wait While (wait for a delegate)
Wait Until pauses execution until a delegate evaluates to true, whereas Wait While waits for it to be false before proceeding.
Here's how it looks in scripting:
int fuel=500; void Start() { StartCoroutine(CheckFuel()); } private void Update() { fuel--; } IEnumerator CheckFuel() { yield return new WaitUntil(IsEmpty); print("tank is empty"); } bool IsEmpty() { if (fuel > 0) { return false; } else { return true; } }
This is an easy to read version of Wait Until being used, with an external function being used to evaluate the condition.
However, you might not write it out like this.
As it's possible to achieve the same result with a Yield Return Null statement inside of a While Loop, (like in the example earlier) you might not think to use Wait Until or Wait While at all.
That is unless you had a specific reason to do so, for example, to add extra logic to the true and false conditions in a function that sits outside of the Coroutine.
A benefit, however, of using Wait Until or Wait While instead of a While Loop is convenience. When using the lambda expression, it's possible to check variable conditions, just like you would in a While Loop, on a single line of code.
Like this:
IEnumerator CheckFuel() { yield return new WaitWhile(() => fuel > 0); print("tank is empty"); }
More information about Wait Until and Wait While can be found in the Unity documentation.
Wait For End of Frame
This specific instruction waits until Unity has rendered every Camera and UI element, just before actually displaying the frame. A typical use for this would be taking a screenshot.
IEnumerator TakeScreeshot() { // Waits until the frame is ready yield return new WaitForEndOfFrame(); CaptureScreen(); }
If you're interested you can find an example of how to actually capture the screenshot here.
Wait for another Coroutine
Finally, it's possible to yield until another Coroutine, that's triggered by the yield statement, has finished executing.
Simply follow yield return with a Start Coroutine method, like this:
void Start() { StartCoroutine(MyCoroutine()); } IEnumerator MyCoroutine() { print("Coroutine has started"); yield return StartCoroutine(MyOtherCoroutine()); print("Coroutine has ended"); } IEnumerator MyOtherCoroutine() { int i = 3; while (i>0) { // Do something 3 times i--; yield return new WaitForSeconds(1); } print("All Done!"); }
The code will continue after the Coroutine you start completes.
Speaking of starting Coroutines…
How to start a Coroutine
There are two methods of starting a Coroutine.
You can start a Coroutine using its string, like this:
void Start() { StartCoroutine("MyCoroutine"); } IEnumerator MyCoroutine() { // Coroutine business... }
Or you can start a Coroutine by referencing the method name (like you would with a regular function).
Like this:
void Start() { StartCoroutine(MyCoroutine()); } IEnumerator MyCoroutine() { // Do things... }
Both techniques are overload methods of Start Coroutine.
Mostly they're very similar, but with a couple of key differences.
Firstly, there's a slight performance hit for using the string method over the name method.
Also, when starting a Coroutine using a string, you can only pass in one parameter, like this:
void Start() { StartCoroutine("MyCoroutine", 1); } IEnumerator MyCoroutine(int value) { // Code goes here... }
However,
You'll notice the biggest difference between using one method over another when you come to actually stop it.
How to end a coroutine
Coroutines end automatically once their code has been executed. You don't need to explicitly end a Coroutine.
However,
You may wish to end a Coroutine manually before it has finished. This can be done in a few different ways.
From inside the Coroutine (with Yield Break)
Simply adding the yield break statement will end a Coroutine before it's finished.
This can be useful for exiting a Coroutine as a result of a conditional statement, for example.
Like this:
IEnumerator MyCoroutine(float value) { if (value > 10) { // Do one thing yield break; } // Do another }
This allows you to create conditional code paths that can exit out of Coroutines.
But what if you want to stop a Coroutine unexpectedly. For example, you want to cancel what the Coroutine was doing entirely.
Luckily, Coroutines can also be stopped externally.
End from outside of a Coroutine (with Stop Coroutine)
How you end a Coroutine depends partly on how you started it.
Stop a Coroutine using its String
If you started a Coroutine using its string, you can use that same string to stop it again.
Like this:
StopCoroutine("MyCoroutine");
If, however, you've started multiple Coroutines using the same string, all of them will be stopped when using this method.
So what if you want to stop one specific instance of a Coroutine?
How do you do that?
Stop a Coroutine by reference
It's possible to stop a specific Coroutine instance if you store a reference to that Coroutine when you start it.
Like this:
bool stopCoroutine; Coroutine runningCoroutine; void Start() { runningCoroutine = StartCoroutine(MyCoroutine()); } void Update() { if (stopCoroutine == true) { StopCoroutine(runningCoroutine); stopCoroutine = false; } } IEnumerator MyCoroutine() { // Coroutine stuff... }
Whichever method you use, it's generally good practice not to mix and match.
For example, if you start a Coroutine with a string, that's how you'll need to end it.
If, however, you're in any doubt, there is one easy method that is guaranteed to stop a Coroutine.
Stop All Coroutines on a MonoBehaviour
The easiest and most surefire method for stopping a Coroutine is by calling Stop All Coroutines.
Like this:
StopAllCoroutines();
This stops all Coroutines started by the script on which it is called so it won't affect other Coroutines running elsewhere.
It does, however, stop Coroutines that were started by the behaviour on which it was called, even if they're in other scripts on other objects.
For example…
If Script A runs a Coroutine that starts a Coroutine on Script B, calling Stop All Coroutines from Script A will stop both of them.
However, using the same example, if you call Stop All Coroutines from Script B. Even if you call it from inside the Coroutine on that script, it won't stop anything, because Script A started it.
So remember to call Stop All Coroutines from the behaviour that started them.
Otherwise, it won't work.
Does destroying the Game Object stop the Coroutine?
Yes, it does.
Destroying or disabling a Game Object will end any Coroutines that were called from it, even if they're in other scripts on other Game Objects.
However,
This only works at the Game Object level. Disabling the script is not enough to stop the Coroutine.
And if a Coroutine on one object was called by a different script, even destroying that object will not end the Coroutine, as Coroutines are tied to the Game Object that called them.
What does "Not all code paths return a value" mean?
If you're seeing this error when writing a Coroutine (probably as soon as you start writing it) don't worry.
This only appears because, as Coroutines have a return type (IEnumerator) there needs to be something to return. In this case, you need a yield return or yield break statement somewhere in the code block and, chances are, you just haven't written it yet.
It's usually best to just ignore the error but, if you want to avoid seeing it while you're writing the Coroutine, then you can add yield break to the bottom of the function.
However…
Unless you're stopping it early, you shouldn't need to end a Coroutine with Yield Break, as Coroutines end themselves once they complete.
So, if you've finished writing your Coroutine, and you're still seeing this error then it's probably because your function doesn't include any yield statements at all.
Which may mean that you're not actually splitting your logic across multiple frames (i.e. your Coroutine isn't waiting for anything to happen).
If this is the case then, chances are, you don't actually need to use a Coroutine at all and it's probably better to use a regular function instead.
Coroutine Best Practices
Coroutines can be extremely helpful.
But… they're not always the best option.
It's important to remember what's good (and what's not so good) about Coroutines so that you can get the most out of them, know when to use them and know when to try something else instead.
For example…
Avoid overlapping logic when using Coroutines
It's possible to trigger multiple Coroutines that overlap, with each trying to execute the same logic and change the same values all at once.
Not great…
While you can't explicitly check if a Coroutine is already running you can usually avoid this by simply stopping any Coroutines on a script before triggering a new one.
This may or may not be an option for you (depending on how many Coroutines you're planning to run at once) but, for the most part, it's an easy safety net.
Set and forget
Coroutines work best when you can 'set and forget' them, so it may help to avoid any logic that needs to change while the Coroutine is executing.
Remember the To-Do list example earlier in this post?
The tank knows what to do, and in what order to do it in.
Plus, with the use of some conditional logic, it could even know what to do when presented with a number of different situations.
However,
Trying to make changes or give the tank new instructions while it's working through its list could become problematic. We'd have to start checking the state of the tank with booleans to know what it was up to.
Which is exactly what we were trying to avoid by using a Coroutine in the first place.
Coroutines vs Invoke vs Async
Coroutines can be great, but sometimes there may be other options that are more suitable.
For example, if you just want to delay the start of a function, Invoke will do that for you, while Invoke Repeating is an easy choice for repeating the same function over and over.
It's also possible to use Async and Await functions in Unity, which work in a similar way to Coroutines.
One of the biggest differences between Async and Await functions and Coroutines is that they can return a value while, generally, Coroutines can't do that.
So which is the best option to use?
Like with many tasks, there is often a method that is technically the best.
However, this is often true of most things.
Sometimes the best method for you to use is the option that's most appropriate but that's also within your ability to execute confidently.
And if that's Coroutines, and they're a good fit for what you're trying to do, then you should use them.
Now I want to hear from you
I want to know if you've been using Coroutines in your project?
Have they helped you, or have they actually caused you more trouble than they're worth?
Or maybe you've got a tip for using Coroutines that other people need to know.
Whatever it is, let me know by leaving a comment below.
Image Attribution
My favourite time-saving Unity assets
Rewired (the best input management system)
Rewired is an input management asset that extends Unity's default input system, the Input Manager, adding much needed improvements and support for modern devices. Put simply, it's much more advanced than the default Input Manager and more reliable than Unity's new Input System. When I tested both systems, I found Rewired to be surprisingly easy to use and fully featured, so I can understand why everyone loves it.
DOTween Pro (should be built into Unity)
An asset so useful, it should already be built into Unity. Except it's not. DOTween Pro is an animation and timing tool that allows you to animate anything in Unity. You can move, fade, scale, rotate without writing Coroutines or Lerp functions.
Easy Save (there's no reason not to use it)
Easy Save makes managing game saves and file serialization extremely easy in Unity. So much so that, for the time it would take to build a save system, vs the cost of buying Easy Save, I don't recommend making your own save system since Easy Save already exists.
Source: https://gamedevbeginner.com/coroutines-in-unity-when-and-how-to-use-them/
0 Response to "Only Continue Code After Coroutine Finishes"
Enviar um comentário