Coding into the Void

Coding into the Void

A blog that I’ll probably forget about after making thirty-one posts.

Better Text Boxes in Unity Part 1: Learn to Wrap

If you’re an indie developer, you probably work a lot with text boxes. There’s a lot of fiddling around you can do: display them all at once vs. animate them, text speed, font, etc. There’s room for stylistic interpretation in all of these subjects, but some choices1 will make your game seem unpolished.2

The biggest offender? Words jumping from one line to another while animating text.

Many indie games naively perform word wrapping, which causes text to reflow to the next line as the word is revealed. This mechanism provides a visual distraction while the user is trying to read.

To see how then naive approach looks compared to a proper approach, see the following example. Left click to animate text, right click to toggle wrapping.

Text jumping from one line to the next is jarring and will distract the reader.

Thankfully, if you’re using TextMesh Pro,3 this problem has essentially been solved for you. If you’re using a different text solution, like Unity’s built-in one, that has rich text support, it isn’t too bad. If you’re not, well, then you’re going to have to do a lot of math yourself.4 5

Alternatively, of course, you can insert your newlines manually into all of your strings. If you have a few, this isn’t the most onerous approach, but it is error-prone and you’d need to redo it if you wanted to change the size of the font or text box.

TextMesh Pro

TextMesh Pro handles this for you. TMP_Text (and subclasses, like TextMeshProUGUI) have the maxVisibleCharacters property. All you have to do is set this to the number of characters to show, and it will handle the wrapping correctly for you. It’s easy! 6

IEnumerator UpdateText(TMP_Text textObj, String textToDisplay) {
    textObj.text = textToDisplay;
    textObj.maxVisibleCharacters = 0;
    while (textObj.maxVisibleCharacters < textObj.characterCount) {
        textObj.maxVisibleCharacters += 1;
        yield return new WaitForSeconds(0.1f);
    }
}

The above coroutine will animate in all characters, then exit. You’d probably want some sort of signal that the text box is done animating, as well as some mechanism for skipping past text boxes.

Rich Text Support

However, when I started out I didn’t know about that property, nor was I using TextMesh Pro. Instead, I was using Unity’s built-in mechanism.7 The following solution is also fairly easy, but it limits your options more than the previous solution.8

While you’re animating your characters, don’t truncate the string based on the number of visible characters. Instead, add a <color=#00000000> tag before the text that shouldn’t be rendered. The #00000000 is the hexadecimal representation of black with an alpha (opacity) of zero.

This way it will render the words correctly, as it will be laid out based on the entirety of the text. If you have tags in your text other than this one color tag, your approach will need to be more complicated, as it will need to correctly elide color tags in the invisible block and avoid splitting within tags.

IEnumerator UpdateText(Text textObj, String textToDisplay) {
    int visibleChars = 0;
    while (visibleChars < textToDisplay.Length) {
        visibleChars++;
        textObj.text = textToDisplay.Substring(0, visibleChars) 
            + "<color=#00000000>" 
            + textToDisplay.Substring(visibleChars);
        yield return new WaitForSeconds(0.1f);
    }
}

The invisible text tag will cause the rest of the text to not be rendered but be used for computing line widths, allowing for proper wrapping without having to do bounds calculation yourself.

Conclusion

If you’ve done it right, your text box should animate like the one at the top of the screen.

This small tweak will make your game feel more polished, whether you use text boxes often or infrequently. As long as you do this and have your text boxes skippable with text speed controls, text boxes probably won’t be the reason that people stop playing your game.9

If you’re looking for an example of a game knocking it out of the park when it comes to text box animations, check out the Ace Attorney series.


  1. Going the path of least resistance is also a choice. ↩︎

  2. Unfortunately, if you start noticing this you will see it everywhere in indies. I can’t recall seeing it in a AAA, but I’m sure someone was sloppy somewhere. ↩︎

  3. TextMesh Pro is a free package to download that is far superior to the built-in Unity text rendering. If there are any compelling reasons not to use it, I’m not aware of them. ↩︎

  4. Please just install TextMesh Pro and save yourself lots of time, unless you’re looking at this tutorial for something other than Unity, in which case, good luck. ↩︎

  5. This actually depends on what you’re using. The demo above uses Phaser 3, which doesn’t have support for rich text in bitmap fonts, but it does provide a mechanism for getting the text with newlines added in the appropriate positions. ↩︎

  6. The docs for TMP say that it’s also more performant, but I’m skeptical that these allocations are making significant impacts. Who knows, though, I could be wrong. ↩︎

  7. Even though I switched to TextMesh Pro, my layout code is intertwined with the old approach. I don’t use maxVisibleCharacters at all, although I might rewrite my code as I work on this tutorial series, if I end up writing more than one post. ↩︎

  8. For instance, existing color tags in the string won’t behave well while “invisible”, as they’ll happily override the color themselves. In my own system, I close out all the tags before inserting the color tag, but that runs into its own issues with sizing customizations. ↩︎

  9. It may sound petty, but slow text boxes is probably the second most common reason that I stop playing a game. The first, of course, being that I’m not enjoying the gameplay. ↩︎