Gamedev Grievances #10: Coloured Text

First of all, a big thank you to all of you who checked out my post last week and gave feedback! I had no idea that so many other programming languages used dynamic typing – I always thought it was just GML which did that. Oh well, the more you know. The funny thing about GML in general is that it seems to be a big mish-mash of lots of different programming languages, and as such it’s a little tricky to categorize and explain at times, but anyway.

Last Screenshot Saturday, I posted this screenshot on my Twitter:

screen-010-2016-09-19-coloured-text-tutorial

Notice that pretty, colourful text in the textbox. For many gamers, this seems like a staple of RPG textbox systems. But in terms of game development, getting this to work can be really quite difficult, and I was very happy I finally got this up and running. So this week, I thought I’d share the strategy I used to make this (eventually) work.

Starting From Scratch

Part of the problem is with the way good old GameMaker: Studio works. Essentially, GM:S gives you the bare bones needed to make a game – things like sprites, sounds, fonts and so on. But anything beyond that is up to you to design and implement – including things like textbox systems, RPG movement, battle systems, and so on.

In one way, that’s actually a really good thing. It gives game developers a lot of flexibility as to how they design their games: not being automatically lumped into a single style by a bunch of templates. But on the other hand, building almost everything from the ground-up can get tedious and complex – as well as making sure all these custom-made elements of your game work together properly.

So without further ado – keeping in mind that we’re building a textbox system totally from scratch – here’s the problem in a nutshell…

The Problem

Obviously a textbox has to have certain functions, like displaying text, showing different texts, and so on. Which means we need to think up some general implementation strategies for making these work. For example:

  • Displaying text and a background. Well, that’s easy in GM:S. Just get the textbox object to draw the current text string and a background. Easy.
  • Displaying the text incrementally – in other words, that scrolling text effect you see in most RPGs, where the letters are revealed sequentially. Again, easy. Just set a variable for the number of characters to show, which increments every step of the game.
  • Advancing to the next text. Store the list of texts to reveal in an array or data structure, then once one text finishes, when the player presses a key start displaying the next one. Done – next.
  • Pauses, slow text, and other fancy effects. Hmm, this seems a bit tricky… how about having an annotated text system, where the text string going into the textbox has certain “keys” to tell the textbox when to do what? Then, when the textbox is revealing text and recognizes a key, just perform the action at that point and delete the key from the text (so the player doesn’t see it)!

    A screencap of some annotated text for the dialogue seen above. (Also... the Fat Controllers.)
    A portion of annotated text in the “text script” in Ambience.
  • Mugshots. These need to zoom in and out, and change expression when need be. Again, a bit tricky. You could either get the textbox object to just draw the mugshot as well as the text – or you could make a separate mugshot object which interacts with the textbox. (I went for the latter option.)
  • Coloured text. This isn’t just a case of colouring the whole string of text at once. This is a case of colouring specific sections of text in the overall string, while keeping the rest the default colour. Oh, and it should also permit different colours to be drawn in the same bunch of text, if need be.

The reason why this one’s so difficult is because it actually impacts on the very first requirement – how the text is displayed in the first place! GM:S has a simple draw_text function, which most people would use to draw the entire text string all in one go. But the draw_text function can only draw a string in a single colour. That means that if we’re going to draw a single string in different colours, we’ll need to call the draw_text function more than once. But how?

Let’s consider, for example, how we might go about drawing this text, formatted as shown:

The quick brown fox
jumps over the lazy dog.

There are two main strategies I’ve found for doing this…

Option 1: The Indent Method

This basically involves drawing segments of normal and coloured text separately, by calling separate draw_text functions for each segment. This means that each new segment has to be indented by the right amount to ensure it’s drawn in the right place. (It’s the system used in this GameMaker textbox engine.)

Option 1: The Indent Method.

This method still has some problems, though. For example, we’d have to calculate the physical length by which each segment needs to be indented. For monospace fonts that’s easy – it’s just the number of characters times the width of each character – but for a non-monospaced font like I’m using in Ambience, that’s not so simple. Using this method also means you’d have to keep track of where the new lines are, to ensure the correct indentation length is used. (We want the word “over” to be indented by the length of the word “jumps”, not by the length of “The quick brown fox jumps”.)

Also, dealing with new lines could present a problem – for example, if the sentence continued after “the lazy dog”, how would we make sure the new line didn’t align with the start of that segment, but rather the edge of the textbox? We could make each line its own segment, then draw the segments with the correct alignment, but that still seems a little fiddly. Or, we could continue that theme and keep splitting the text into finer and finer segments until we get…

Option 2: The “Every Letter” Method

This is the more “brute-force” method of the two – it involves drawing each letter individually, with its own colour. (From what I can gather, it’s used in Undertale‘s textbox system.) Effectively, each segment is now only one character long, and can be any colour it likes.

option-2-every-letter

At first glance, that makes things much easier – no more worrying about where the coloured text is, or the position of indents, or the printed length of multi-character segments! However, this method is, as you can probably imagine, a bit more CPU-intensive. In particular, it involves using a “for” loop (to cycle through and draw each printed character) in GM:S’s Draw event, which isn’t optimized to deal with that kind of computational load.

Also, multi-line text still isn’t just drawn “as-is”, but needs to be dealt with manually to ensure all the characters aren’t just drawn on one line. New lines also need to be added automatically, without the need for a new line symbol.

The Solution:

I went with the second option, mostly because I didn’t want to deal with the potential problems that indents might also cause for multi-line text. In particular, if push came to shove and I couldn’t find a way of dealing with pesky multi-line text, I really didn’t want to have to go through all my writing and change the formatting – for example, by adding new-line characters in each and every place where a new line was required. It also meant I could make use of the “annotated text system” I talked about earlier to easily control text colours.

To deal with the “new line” problem, I modified the text annotation system (which monitors the text as it’s being revealed) and made it add a new line character “#” whenever it detected the next word would run over the end of the textbox. These new line symbols were then detected by the text drawer, which incremented the drawing y-coordinate each time a symbol was detected (that still had to be done manually, unfortunately).

coloured-text-device-unlocked

Some preliminary testing in a few different areas worked fine, and despite what I thought might happen, there was pretty much no observable decrease in performance. Looks good!… for now. Like many of these things, we’ll have to see how it works out long-term.

Be the first to comment

Leave a Reply