Scratch has several code blocks that can take time to complete. For example, you might chain together several “say” blocks to present a conversation. Each block will then pause the execution of that stack until it completes. Up to this point, all of the code we have learned has been synchronous. This means that even if you may know enough to change the text on your UI, that each statement would run one after another so fast that you wouldn’t be able to read each message. Not to worry, we can also obtain a similar result, and we will learn all about it in this lesson.
Getting Started
First, let’s create a new Scene named “Conversation”. From the file menu, choose “File” -> “New Scene”. Then choose “File” -> “Save As…” to give it a name.
Now, let’s create a label that we can print various messages to. From the “Hierarchy” pane’s “Create” button, choose “UI” -> “Text”. Feel free to edit the text’s size, color, and any other attribute to your liking via the inspector. You may also choose to change the background by editing the Main Camera.
This will actually create multiple Game Objects. If you look in the Hierarchy, you will see Canvas, Text, and EventSystem have all been created. The Canvas GameObject is the root object of all user interface objects that will be added. The Text GameObject is the actual label we will be modifying. The EventSystem object handles events that are important for many User Interfaces (like mouse clicks etc), though we will not need it in this lesson.
Use the “Project” pane’s “Create” button to create another script, and name it “Conversation”. Then attach the script to the “Text” GameObject.
Double click on our “Conversation” script in the “Project” pane to open it in the code editor. We will need to add a new using directive at the top of our script in order to work with the text label:
[csharp]
using UnityEngine.UI;
[/csharp]
Coroutine
To create our asynchronous code, we will use something called a coroutine. A coroutine is like other methods, except that it has a unique ability to hold a sort of book-mark in its execution. Code can run up to a point in the method, pause for some amount of time, and then continue running right from where it left off.
To see this in action, modify the definition of the “Start” method by changing its return type from “void” to “IEnumerator” like so:
[csharp]
IEnumerator Start()
[/csharp]
Unity will still call this method for us automatically, just like it used to. However, it is now able to run over time. Add the following statements to the body of “Start”:
[csharp]
var label = GetComponent
label.text = “Hello”;
yield return new WaitForSeconds(1f);
label.text = “My name is Jon”;
yield return new WaitForSeconds(1f);
label.text = “What’s yours?”;
[/csharp]
In this code snippet we begin by creating a local variable named “label” which holds a reference to the “Text” component on the same Game Object. The reference is obtained using the “GetComponent” method. The type of component to get is listed inside the less than and greater than signs.
We then immediately set the text to say “Hello”. Both of the first and second statements are run right away, much like the synchronous code we have written elsewhere. The asynchronous aspect appears with the third statement. We use the “yield” command to cause execution to pause. The “WaitForSeconds” controls how long that pause will be – one second.
After one second has passed, we pick up right where we had left off. We still have the local reference to our Text component, so we can assign its text to be “My name is Jon”. We then wait for another second and print one final message before the Coroutine is completed.
Save your script, then head back to Unity and press play to watch our code run over time.
Chaining Coroutines
In the first example, we chained together multiple statements in a single Coroutine over time. It is possible to make things even more powerful by chaining together coroutines. We can do this by starting a coroutine from within a coroutine and waiting on that coroutine to finish.
Let’s change our conversation code so that it is typed out, one letter at a time. You see that effect in a lot of games where a character is speaking. Add the following method:
[csharp] The code above starts by getting a reference to our Text label, just like before. Then we do something called a loop. In Scratch, this might be similar to a “repeat” block from the “Control” category. This particular type of loop is called a “for” loop and it holds three bits of information. First, it initializes an iterator variable which commonly is named “i”. After the semi-colon, we tell the “for” loop how long it should repeat – in this case for as long as the value of “i” is less than or equal to the number of characters in the message. After the next semi-colon, we have code that runs after the each loop step, which is used to modify our iterator. We use “++i” to say “increment the value of i by one”. The “for” loop has its own body, much like a method and class do, which is marked by the open and close bracket. The two statements inside are going to be repeated with the loop. Inside of our “for” loop’s body, we assign the label’s text to be a “Substring” of the whole message that begins at the beginning (programmers start counting from ‘0’) and spans “i” number of letters into the message. Since with each loop the value of “i” will increase, then we will see more of the message appear over time. We use another “yield” for one second after “typing” each letter. Now change the “Start” method to look like this: [csharp] Now we are using the result of “StartCoroutine” as something to yield by. The parameter that we pass this method, is the result of us calling our other Coroutine and passing the message we want it to present. Taking everything together now, the first statement will pause execution until all of the letters of “Hello” have been typed out onto the screen. Then we use the “WaitForSeconds” to pause for an amount of time, and continue the same pattern with the other messages. Save your script, then head back to Unity and press play to watch our cool new effect. If you liked the “loop” we used to print out letters, you might like to see it used again. We can “refactor” our Start method to run in a loop since we are following a sort of pattern. [csharp] for (int i = 0; i < messages.Length; ++i)
{
var typer = TypeOut(messages[i]);
yield return StartCoroutine(typer);
yield return new WaitForSeconds(1f);
}
}
[/csharp]
In the code above, I have grouped all of the messages into a single variable called an array, specifically a string array. Our “messages” array can hold any number of comma separated strings that we would like to give it, and the rest of the code will continue working without any changes. We could even decide to turn our “messages” variable into a field of our class, and then we could assign these messages in Unity’s inspector or provide it by some other kind of resource like a text file. The for loop looks very similar to the last one, we iterate on “i”, until we have looped once for each message in the array. In the body of the “for” loop we can grab a particular message by indexing into the array. We specify which message we want within the square brackets. Like before, programmers start counting at zero, so our first message appears at index ‘0’. The other code, for starting a coroutine and waiting for one second, are the same. While the “TypeOut” effect is pretty cool, some of our users may get impatient, especially if they read quickly and the conversation is very long. It would be nice to be able to skip the TypeOut of each message to help speed things along. Just like we are able to “StartCoroutine”, Unity also has the ability to “StopCoroutine”. You should note however that if you are chaining Coroutines like in this example, that a stopped coroutine will not finish, and so if another coroutine is waiting on it to finish, it will end up waiting forever. Another solution is to to end a Coroutine using “yield break” which will return early from a Coroutine. It will still be shown as a “completed” coroutine even though it didn’t execute all of its statements. To implement this feature, start by adding a new field: [csharp] This variable will indicate to our TypeOut method whether or not it should skip its own effect. We will mark this flag as true inside of the Update loop, in the case that any key has been pressed: [csharp] And we will check for this opportunity to skip, inside of the “for” loop body of our TypeOut Coroutine: [csharp] Here we use an “if” statement to control the flow of execution. The statements inside of the body of the “if” statement will only be executed when the tested condition is true. In this case our “condition” is simply the value held by the “skip” variable. When “skip” is true, we first begin by setting it back to false. This way, our users will have to keep pressing a button for each message that they want to skip. Who knows, maybe they skipped by accident and actually really like the effect! Next, we assign the full message to the label, and finally we abort the Coroutine by using the “yield break” statement. There wont be any additional loop steps made, and the two statements after the “if” will not be run on this loop step. At this point our script is complete. The full script should look something like this: [csharp] public class Conversation : MonoBehaviour // Start is called before the first frame update for (int i = 0; i < messages.Length; ++i)
{
var typer = TypeOut(messages[i]);
yield return StartCoroutine(typer);
yield return new WaitForSeconds(1f);
}
}
IEnumerator TypeOut(string message)
{
var label = GetComponent Save your script, then head back to Unity and try out each of our new features. Press play and allow the message to type out a letter at a time, or press a button to skip that effect and see the entire message all at once. If you want to dive a little deeper on the subject of Coroutines in Unity, then these links will help: In this lesson we learned about the differene between synchronous and asynchronous code. We learned that we can use asynchronous code to write things that occur over time. As an example, we created a simple scene that prints messages to the screen. We made it so that each letter of each message types on to the screen one at a time, and allowed users to hurry things along by using keyboard input. We implemented our asynchronous code using Coroutines and learned about the various methods Unity has in place to work with them. If you find value in my blog, you can support its continued development by becoming my patron. Visit my Patreon page here. Thanks!
IEnumerator TypeOut(string message)
{
var label = GetComponent
for (int i = 0; i <= message.Length; ++i)
{
label.text = message.Substring(0, i);
yield return new WaitForSeconds(0.1f);
}
}
[/csharp]
IEnumerator Start()
{
yield return StartCoroutine(TypeOut(“Hello”));
yield return new WaitForSeconds(1f);
yield return StartCoroutine(TypeOut(“My name is Jon”));
yield return new WaitForSeconds(1f);
yield return StartCoroutine(TypeOut(“What’s yours?”));
}
[/csharp]Polish
IEnumerator Start()
{
var messages = new string[]
{
“Hello”,
“My name is Jon”,
“What’s yours?”
};Aborting a Coroutine
bool skip = false;
[/csharp]
void Update()
{
if (Input.anyKeyDown)
{
skip = true;
}
}
[/csharp]
if (skip)
{
skip = false;
label.text = message;
yield break;
}
[/csharp]Demo
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
{
bool skip = false;
IEnumerator Start()
{
var messages = new string[]
{
“Hello”,
“My name is Jon”,
“What’s yours?”
};
for (int i = 0; i <= message.Length; ++i)
{
if (skip)
{
skip = false;
label.text = message;
yield break;
}
label.text = message.Substring(0, i);
yield return new WaitForSeconds(0.1f);
}
}
void Update()
{
if (Input.anyKeyDown)
{
skip = true;
}
}
}
[/csharp]
Links
Summary