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()
{
}
IEnumerator CoroutineName()
{
yield return new WaitForSeconds(2f);
}
Different Wait Objects We Can Call In A Coroutine
WaitForSecondsRealTime – suspends the coroutine execution for the given amount of seconds using unscaled time
How To Call A Coroutine
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
private void Start()
{
StartCoroutine("PrintToConsole");
}
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");
}
private void Start()
{
StartCoroutine("PrintToConsole");
}
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");
}
WaitForSeconds And WaitForSecondsRealtime
IEnumerator PrintToConsole()
{
yield return new WaitForSecondsRealtime(2f);
Debug.Log("Printed after wait time");
}
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, 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");
}
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
int countdown = 3;
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;
}
}
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;
}
}
IEnumerator PrintToConsole()
{
yield return new WaitWhile(AllowToPrint);
Debug.Log("Printed after waiting for the delegate");
}
Coroutines Can Take Parameters
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: