I have a teeny tiny pet peeve with dialogue boxes. Er, not dialog boxes — dialogue boxes, the ones in video games with scrolling lines of dialogue.
I recently wrote a dialogue box, and I saw a game that made this mistake, so here’s a post about it.
Here’s a live example of the above animation. (You can double-click on any of these to restart them.)
And the code responsible. I wrote this in the form of a fairly generic
update() function, rather than in terms of
requestAnimationFrame, to minimize the DOM-specific stuff. All the JS in this post is vanilla DOM.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
If you’ve ever written dialogue handling code, this shouldn’t be too surprising. Multiplying
dt (seconds) by
SPEED (characters per second) produces a number of characters, so whenever
timer is at least 1, another character should be displayed. Spaces are counted as “free”; otherwise, the scrolling would seem to pause between words.
The problem, of course, is that the resulting text looks like this on successive frames, where the
|s mark the edges of the box:
|Demonstrating inadequate word-wra |
|Demonstrating inadequate word-wrap|
|Demonstrating inadequate |
And so on. The renderer has no way of knowing that “word-wrap” is only part of a longer word, so it merrily puts everything on one line. The player then sees half a word abruptly jump to a new line, and judges you harshly for it.
Depending on your environment, you can solve this one of two ways, or not-solve it a third way.
This works well in browser-based games, where you have a comically powerful text rendering engine at your fingertips. In graphics-oriented engines that don’t offer any text rendering beyond “print this text to the screen somewhere”, this approach may not be practical.
The idea is to always “draw” the entire phrase, but implement scrolling by making it partially invisible. Consider this HTML:
Even though the word is split across two tags, the browser must still treat it as a single word, because there’s no space anywhere. So the phrase will be word-wrapped correctly from the beginning, and the problem is solved.
You could implement this with only two
<span>s, as above, but that forces the browser to reflow the text every single frame. It probably doesn’t make a visible difference, but I prefer to wrap each character in its own
<span> and simply make them visible one at a time. As a minor bonus, you can put whitespace in the same
<span> as the preceding letter, and you won’t have to worry about it within your update loop.
Also, if your text contains formatting — i.e., more HTML — then one
<span> per character is much simpler to deal with. (Dealing with it is left as an exercise.)
Here it is live:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
I added an
init() function (called from a
load handler, not shown here) to do the setup and split the string into a series of
<span>s. (If you wanted to be especially clever, you could use the
DocumentFragment API here, but I’m not sure it’d make a real difference.) The main loop becomes much simpler: rather than counting characters, it can use the DOM tree API and hop from one
<span> to the next with
.nextSibling. Once you hit
null, you’ve run out of characters, so you’re done.
The CSS is merely:
1 2 3
Be sure to use
visibility: hidden; here and NOT
display: none;! The latter tells the browser to ignore the hidden characters while rendering, which defeats the whole purpose of having them.
The other fix is to keep drawing one character at a time, but split the phrase into lines once ahead of time.
DO NOT use your programming language’s standard library to do this. DO NOT just Google for code that does this. You will get something that word wraps based on number of characters without taking the font into account, and the results will be wrong.
DO NOT fudge it by guessing the width of the “average” character. You will hit edge cases, and they will look ridiculous.
Find something in your graphics library to do this for you. For example, LÖVE has the poorly-named
Font:getWrap: it takes a string of text and a width, and it returns a set of wrapped strings, one per line.
(Of course, if your font is monospace and will always be monospace, feel free to do naïve word-wrap.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
This code is fairly similar to the original, since the basic idea is the same. All I did was add the
init() step and change the space code to also skip over newlines.
And, hm, that’s all there is to it, really.
Maybe you don’t have a fancy text rendering engine, and you don’t have any way to correctly break the text, and you’re dead set on using a proportional font.
At this point I would be questioning some of the decisions that had brought me to this point in my life, but you do still have one final recourse. The classic solution, dating back decades. Pokémon did it. Come to think of it, Pokémon might still do it.
What you do is: manually include line breaks in your dialogue. All of it. Everywhere.
That is, instead of this:
You will need to literally have this:
I hope at least one person reads this and goes to fix the word-wrapping in their scrolling dialogue. I’ll have made the world a slightly better place. 🌈