The Unexpected Hassles of Colorful Text


Something different today.  I had a somewhat silly time implementing a small feature and thought I'd log what happened, since it was an amusing reminder that simple things aren't always simple.  There's no staggering technical problem or solution here, just a minor comedy of errors, of the sort that we all run into from time to time.

Tutorial dialogues tell you the inputs for various commands, but I felt like they could be easier to read.  Here's what one looks like:

A dungeon view with a text box that reads "Welcome to Minerva Labyrinth! This short mission will introduce you to the basics of the game. Press Space, Enter to advance text boxes like this one!"  All of the text is a very light bluish-grey on a dark blue background.

Well, that's fine, but the keys don't really stand out.  I thought highlighting them in a different color would make them a little more readable.  Unity supports colored text out of the box with rich text formatting, so I just told the tutorial processor to add an automated color tag any time we plug in commands.  

The text box now displays "Press Space, Enter" in yellow, but there is an extra comma and large gaps at the ends of the lines.

Well, that's not right.  Why are the line breaks messed up now?  The problem is that my text box prints text a few characters at a time for a scrolling / typing effect.  Each time I print to the text box, I calculate whether the width of the next full word will fit on the current line or not.  If not, I break to the next line before printing the word.  This is to prevent the scroll from starting to print a word on one line, and then shunting it down to the next line halfway through when it runs out of room.  This is a somewhat common glitch that I see in games with scrolling text.  It's very annoying.

So what does that have to do with the color?  The rich text tag is actual text included in the raw string, and looks like this: <color=#ffe65a>.  This means that it's being included in my calculation of word width, even though it's not printed.  So, the next step is to add code to that calculation to ignore any rich text tags.

That's not enough, though.  We can't just scroll through the tag like normal text; otherwise, we'll print a broken partial tag for several frames, like this:

The text box now reads "Press C", displaying a partial color tag.

 So, when we get the buffered text to print each frame, we check to see if we've encountered a <.  If so, we make sure to grab the entire tag and print it all at once, in addition to the expected number of printable characters.  We could potentially encounter multiple tags - probably not, but it's possible - so we want to pull in the last tag we find within our printed range.

There's another issue there, too, which is the empty key and comma printed before Space.  This happened because when printing out an arbitrary number of inputs, I check the length of the string so far to see if it's time to start printing commas - and again, the RTF tag is being counted there.  So, I'll just prepend the tag after I'm done building the string, instead of at the beginning.

The partially-scrolled text box now displays a color tag, reading "Press color FFE65A Space."

That is definitely not right.  The color works and the line is spaced out properly, but only after it's done scrolling.  While the page is still being printed, this broken formatting appears in the output, because we haven't printed a matching </color> tag to close the formatting.

I thought this might happen, but I wasn't sure if Unity would internally terminate an open tag on its own or not.  This answers my question; it doesn't.  This means I'll need to temporarily close the tag myself each frame until we get to the real closing tag.  I update the code to track whether we've encountered a color tag or not; if we do, append a </color> to the end of whatever we output this frame.

The text box now prints "Press Space, Enter" between visible color tags, but does not change the color. There is an extra closing color tag at the end of the text.

Nope, wrong.  Not only do we have an extra </color> at the end of the scroll when we no longer need it, but the entire rich text format is broken by it being there.  We need to keep track of not just the opening tag, but the closing tag, as well.  Once we've printed the real closing tag, we need to stop printing the artificial one.

The text box now prints correctly again, saying "Press Space, Enter" with "Space, Enter" highlighted in yellow.

And there we go!  The text now flows smoothly, is properly formatted, and fills in the color as it goes without printing any extra junk text.  Let's just test a few more times to make sure we accounted for everything.

Argument Out Of Range Exception: Length cannot be less than zero. Parameter name: length

Huh?  If we don't want to wait for the scroll, we can press a key to advance immediately to the end, but now the dialogue crashes when we do that.  But length is less than zero?  What?  How?

It turns out that this is an overflow error.  When we skip the scrolling, the dialogue controller actually sets the number of characters to print to the maximum value for an integer, essentially telling itself to print everything in the buffer during the current frame.  This means that when we add the length of the RTF tags to that already maxed-out number of characters, we overflow to a negative and our string manipulations that reference that value fail.

Well, if we're printing the entire page at once, we don't need to do all this extra tag processing anyway.  So, whenever we skip scrolling, we just cancel out our temporary tag printing and don't bother with any more checks for this page.  This fixes the error, but there's one more bit of unfinished business:

A dungeon view with no text box visible.  In the bottom right is a white prompt that says "Menu: color FFE65A Tab."

See that menu prompt in the bottom right?  That obviously won't do.  Checking the inspector, I can see that the string is formed correctly, but that particular text readout doesn't have rich text enabled.  I turn that on, and now everything works as it should.

The menu prompt now reads "Menu: Tab, Esc" with "Tab, Esc" in yellow.

On paper, this built-in feature of Unity should seemingly have taken no time at all to implement, but I didn't consider all the reasons that rich text wouldn't play nicely with my existing solutions.  This is something that I decided to do off-the-cuff today, and it ended up taking me several hours to get right.  On the other hand, I also implemented a basic autosave today - something I had been putting off for quite a while, thinking it would be a lot of effort - with just a few lines of code and some small GUI updates.  Funny how our expectations can get flipped around.

Leave a comment

Log in with itch.io to leave a comment.