In: Game Development, Made with Unity, Our projects, Project Libra

If you are creating a visual novel (or any text-heavy game), then surely the first thing you want to build is a nice dialogue box. It may sound simple enough, however, not all text boxes are created equal.

The role of a dialogue box goes beyond simply displaying our story. It should elevate our scenario to a whole new level. It’s much easier to convey emotions like fear, suspense, and joy when we can change the text speed on the fly (or play a specific animation when a certain word comes up on screen).

As a result, our players will feel more involved in the story.

To do so, here’s a list of features I personally wanted for our dialogue box in Project Libra:

  • Text appears one character at a time. The good ol’ typewriter machine effect.
  • Variable text speeds for different dialogue lines.
  • Speed up text animation when holding a specific button.
  • Change the color of the text, can be the whole text or certain words.
  • Special Commands / Events can happen when the text animates.
    • Play a sound at a key moment.
    • Change the text speed at any point.
    • Change a character’s animation while they are talking or listening.
    • Shake the main camera for theatrical effects.
  • Make the whole text shakes.

I’ll try to break down how we managed to build our dialogue box with all these features. It was the first thing I worked on for this project, and many things changed since I started.

I will go over the several iterations I went through personally below. I suggest to read everything before coding your own dialogue box.

The basics

Alright, let’s start with the basics. The simplest dialogue box would be one where we just display the text one character at a time.

In Unity 5, it’s quite easy to create an animated dialogue box. All we need is a script that runs through your string and output one character at a time in a text element. We can do that with a coroutine:

In a coroutine, we add one character to our text element every few milliseconds.

Simple and effective.

(Also added a base speed variable which determines how fast the text should be printed into that box. We can increase the speed when the player is holding a certain input.)

See the code over here.

(You probably realized how the text jumps from one line to another. I talk about line breaks and how to fix this problem later on in this post.)

Special Commands

Try and read the following lines of dialogue:

“I {sound:1}love {shake}caaaaaaaaaaaaaake!”
“It’s {color:#ff0000}delicious.{color:end}”

As you can imagine, the text should display like so in the game’s text element output:

“I love caaaaaaaaaaaaaake!”
“It is delicious.”

While printing the text, the script has to rip the special commands out of the string so it doesn’t show in the game’s final output. Then, when the text prints, it calls a function based on the command found.

Let’s go over what we have: the {shake} command makes the camera shake while a character is talking. The {sound:1} command plays a specific sound here with the ID of 1.

Finally, the {color} command is split into two commands: the start and the end. Being able to color specific words is a nice feature to have if you want to emphasize certain words in a dialogue line.

Let’s take a deeper look into how we can code this feature into our game.

Simple commands

I figured two ways to catch the commands for your typewriting text box:

  • While printing, if the current character is “{“, don’t print it and loop through the next characters until we get the closing bracket “}”. Then send the command to a function that will make the right calls (play a sound, shake screen, etc.); or
  • Get all the commands before printing, organize them all in a list and execute them when the printing text reached the point where it was before we stripped it out. We need to store the begin positions for each command.

I tried both for my game and ended up using the second one.

I created a new SpecialCommand class in order to build a list containing all our commands.

BuildSpecialCommandList(text)

This function returns a list containing all the commands it stripped out from the original dialogue text. It uses GetSpecialCommand() to convert the string {command:value} into an actual command.

GetSpecialCommand(text)

This returns a SpecialCommand. We can then access the command’s name and values later when printing text on screen.

StripAllCommands(text)

Regex* is really useful when we want to validate data. It basically only checks if your text matches a given pattern.

*editor note: regex is love, regex is life.

Regular expressions are commonly used to validate web forms. For example, we can use a regex to make sure the user entered a valid phone number in the phone field.

In this case, I use a regular expression to catch and remove all the commands from the dialogue line, so I can easily strip them before printing the text on screen.

So far, what we accomplished:

  • Building a list of all the commands and which index (position) they are at in the source dialogue string
  • Stripping out all the commands from that string, ready to be printed on screen.

With that done, we now know when and what command to execute. We just need to add a final check in our coroutine in order to run the commands at their appropriate index.

CheckForCommands(int index)

This function will check for us if there is a command in the current index of our string and then execute it.

(Later in the project, I noticed I also needed to loop through my list at every index, just in case there were multiple commands with the same index. I personally feel this bit could be optimized, though right now it works just fine.)

ExecuteCommand(SpecialCommand command)

Adding more commands is easy. All you need to do is implement more checks in this function. Then, you create your own code for whichever effect you are looking for.

Reminder: The spaces between your commands and your dialogue are important! Make sure to only have one space between the actual words, and no more between the commands, or else there will be 2+ spaces between each word. Of course, this is easily noticeable when you test, but if you have large amounts of text in your game, this can be tedious. One way around it is to check for a space, right after a space has been printed, in your coroutine. If the last character was a space and the current is also a space, skip to the next character.

Click here for the code for the special commands.

Advanced commands (pairs)

In Unity 5, you can colorize any part of a text by simply doing this:

<color=#0000ff>Snow</color>

Just doing so will make the word Snow show up as blue in your text element.

Let’s bring back our color command from earlier:

“It’s {color:#ff0000}delicious.{color:end}”

When the script reaches a {color} command, it has to spawn the two markup elements above. However, if we just replace the commands for the markup, we run into a problem:

If you print the markup elements one character at a time, you will see the markup tags show, until they are closed and then the text is colored. That’s a problem.

What we actually want is the text to be already colored before it shows up on the screen.

To do so, you can print every single character inside these two markup elements, individually, until the game gets the {color:end} command.

Of course, that’s just one way to colorize your text. That’s why I’m not giving you the code for this example. There is a better way, and I’ll go over it later in this post.

The Word-Wrapping Problem

In the first iteration of our dialogue box, we had an issue with the text jumping from one line to another, like it does right here:

Well, this sucks. There’s no way for the coroutine to know when a word should start on the next line before printing it.

I came up with three different solutions to this problem:

  • Render all the text ahead of time but entirely transparent, then create the typewriting effect by toggling the opacity of each letter individually;
  • Calculate the width of every character in the current string, then insert line breaks according to the maximum width of our textbox element.
  • Manually add line breaks to our entire scenario. Spoiler alert: Don’t do that.

I’m not going to go over the last solution, as it should be your very last resort. Your time is better spent on working on your story (remember why we are doing this dialogue box.) Manual line breaks should only be used if they serve a stylistic purpose to your dialogue.

Invisible text

To create the effect, we can use the markup elements from before and make the alpha values 0.

<color=#ffffff00>This entire string is invisible.</color>

Then, we would simply pick one character at the time out of the markup elements, then put it at the beginning of the string :

This text is visible <color=#ffffff00>but this isn’t, yet.</color>

Because the text will be inhabiting the textbox from the start, the line-breaks will be kept as is and no words will jump.

Note that if you’re also using the coloring strategy for specific keywords in a dialogue line, you have to be careful about where to spawn the two markup elements. This solution is still valid, you’ll just need to make sure not to confuse the script between the opacity tags and the color tags.

I didn’t bother doing this myself. It’s just one way to do it.

Calculate box and text width

The second solution sounds great on paper. In fact, I really wanted it to work.

In theory, it should be quite easy to add line breaks automatically to our string. We simply need to calculate the width of each character in our string and then see if the current word overflows in our text box.

It’s possible in Unity 5 to calculate the width of a specific character in a given font. This is great for us, since we don’t have to change anything if we ever decide to change the font of our dialogue box. It should also work on different resolutions. Great!

maxWidthBeforeBreak

I determined a max width before we add a linebreak. I iterated through my filtered string (without commands) and kept the sums of every characters’ width, which I stored into lineWidth (that name could have been better).

I also kept track of the last index I had an empty space in my string, in lastSpace.

If lineWidth > maxWidthBeforeBreak, then we put a line break after the last space (lastSpace). And, boom! This should do the trick.

Updated code for this part.

Warning: This code ended up not working for me in the long run. See below.

Inconsistencies with character’s width

For some strange reason, Unity did not always return the right width for a specific character, which messed up the linebreak calculations.

After exhausting researches, it seems the Canvas was interfering with the code above. If the UI Scale Mode is set to Scale With Screen Size, the code that checks the width for characters stops returning the right values.

I needed the Canvas to be set to these settings, so the user interface can scale based on the player screen. (Note that, if you target just one resolution and/or aspect ratio, the code above should work, although I advise you to read more below for something much better.)

Not only the values were wrong, sometimes I’ve seen the width of a character return nothing. I couldn’t figure out what made it behave this way. Sometimes the value was completely different from what I expected. It changed every time I clicked play.

I found no solution online for the problem.

(I saw a single mention in a comment section suggesting that the canvas may be the culprit. I wanted to link it, but I lost the source. Hell, as I’m writing this, the line break code above DOES work, so what do I know?)

The power behind TextMeshPro

Because I couldn’t figure anything out how to fix the code above, I continued working on other features for my game.

We now can animate characters and change their animation when we want.

Our dialogue is now stored inside JSON files, easy to modify. The code reads it like an actual scenario.

Up until now, I still had trouble with line breaks in my dialogue box.

Enter TextMeshPro.

TextMeshPro provides a tall list of features for everything you can think of: more control over the text format, advanced text rendering, and the list goes on. You should use this in all your projects. Seriously.

With TextMeshPro, it’s actually pretty easy to create the typewriter animation. Hell, there’s even a demo showcasing it in the bundle.

If we return to our basic code with our coroutine, all we need to do is replace our text object with the new TextMeshPro Text object.

can now use maxLetterDisplayed in order to animate our text.

Instead of adding one letter to our text object at a regular interval, we can initiate maxLetterDisplayed = 0 and increment it by 1 after a few milliseconds.

The text is already formatted. TextMeshPro just shows or hides a set number of characters. This means the text is already placed on our screen and will wrap correctly.

Finally, the text doesn't jump!

You may have a look at the updated code here.

Additional pros

I didn’t mention it before, but text alignments were also a problem with our previous version.

We couldn’t center a line of text because doing so would mean the string would start in the middle and expand from there, instead of showing at the right-most position and end up centered. TextMeshPro fixed that as well.

Shaking text effects

TextMeshPro comes with several demos, including a shaking effect for the letters:

While this demo looks awful, we are going to use it as our base in our code.

However, there seems to be a problem with my current script. We can’t make the text shake while it’s not currently displayed on the player’s screen.

It looks like maxLetterDisplayed has some sort of limitation, but screw that, I want my text to shake while also appearing on screen.

In order to make this work, I needed to take a look closer at the shaking demo. The demo is using the four vertices of each character in order to make them shake.

Therefore, if a character is not rendered, the script breaks and the game crashes.

Before this discovery, I was certain that, with maxLetterDisplayed, the text was still in the scene and some parts are only hidden, but it seems the script can’t get the vertices when they aren’t displayed.

The characters are probably not rendered into the text box element at all, and therefore can’t be accessed.

This means we can’t pre-emptively make our text shake before displaying it without making some changes first.

The solution I came up with is a recall to the first solution I found earlier for our line break problem. What if, instead of using maxLetterDisplayed, we animate the opacity of each character in our text box?

Technically, the text is rendered on screen; its alpha is just set to 0. This means the script can actually get the four vertices it needs for the shaking effect I (desperately) wanted.

There’s even a demo in the TextMeshPro folder where they show you how to modify a specific character’s color by using their vertices!

We set the opacity to 0 for all our characters. Then, we make it shake. Finally, we do the typewriter effect by changing the opacity of our characters one at a time.

Note: with some changes, the {color} command should also use this script. It’s much easier and we don’t need to use markup elements in our text box anymore. Simply change the color of each character to the one we find in our color command. When we get the {color:end} command, we go back to the base color.

Check out this updated code here.

What’s Next

That should be it for my dialogue box. The next thing we could do is make the coroutine pause longer when the current character is punctuation. This would make the conversation feel more natural.

Also, consider having no pause when the current character is a space. This should remove the weird pause between each word.

(I completely forgot to do these in my own game. I just thought about it while thinking of other things to do for this article.)

We could also add various text speed for our game. This way, our actors don’t always talk at the same pace. Emotions can be easier to understand for your audience when you take time to implement these subtle details. And text speed is just one way to do it.

In my game, each person has a voice audio clip, that beeps along with the typing. The dialogue box script knows which person is currently talking and plays the right audio clip accordingly. The voice clip plays at around the same interval as the text animating on screen.

Your scenario/dialogue could also be stored outside your game, especially if you’re looking to have your game localized. It’s also easier to edit.

Here are all the examples found in this blog post.

You may have a look at the code behind every single demo we made for this blog post here.

Finally, The End

Here lies the end of my very first public blog post. I hope this experience can somehow help some of you.

Here are some more resources and what I used, for reference. If you have any questions, don’t hesitate to post a comment below! We will read them all and we’d be glad to help.

Resources, Research & Reference