Dialogue System โ The Nerd End of Things
Added 2018-10-15 16:15:59 +0000 UTC
Hello! My name is Miguel, and I'm the programmer in AstralShift, currently working on our upcoming title Little Goody Two Shoes.
Recently, we tweeted out a preview of the dialogue system in the game,
in the form of a gif:

โ Preview of the dialogue system โ
It might seem like a small preview, but besides @Hika's, `@Kira's and @Ichigo's (painstakingly-created, gorgeous) art, and @EvilHairbrush`'s (painstakingly-created, gorgeous) text, there's some of my (ฬถpฬถaฬถiฬถnฬถsฬถtฬถaฬถkฬถiฬถnฬถgฬถlฬถyฬถ-ฬถcฬถrฬถeฬถaฬถtฬถeฬถdฬถ,ฬถ ฬถgฬถoฬถrฬถgฬถeฬถoฬถuฬถsฬถ) stuff going on in the background!
You see, as a programmer, my job is to make the tools that allow the team to make the game, both by making sure that there's, for example, such a thing as a character to control (and that pressing the right keys make it walk and talk), but also by making things that allow the team to create new content or iterate on existing content quickly and easily.
In particular, one aspect that needs a lot of iteration is the dialogue text; even if a lot of the dialogue is written beforehand, some of it might just not work as well when shown in a dialogue box, or EHB might want to change a detail that's mentioned in a lot of places.
In any case, it's simply advantageous (and a good idea in game development, according to important people) to be able to easily experiment and iterate.
To give EHB this freedom, changing text had to be independent from me (different text, same code) and from Hika (different text, same map).
Luckily, since LGTS is being developed in Unity, I can stand in the shoulders of giants.
Quite luckily too, since I'm pretty small in the grand scheme of things.
Alec Holowka, developer in Towerfall and Night In The Woods, when making the latter created a tool specifically for writing branching dialogue independently of the programming.
Named Yarn, the app is based upon Twine, which itself is supposed to be a beginner-friendly writing tool.
Not to be confused with the Javascript Package Manager of unfortunately the same name
The premise of Twine is that interactive text (similar to those choose your
own adventure books) is organized in nodes. These can be connected by hyperlinks, formatted [[๐๐๐๐ ๐๐๐๐|๐พ๐๐ท๐ด๐]] .

โ Twine โ
Similarly, in Yarn, branching dialogue options are laid out as hyperlinks, the main difference here being that one file represents not a full connected story (like you'd write in Twine), but rather a single dialogue event.

โ Yarn โ ๐ค
Besides branching dialogue, there's also some other more complex แตแตแต แตหขแตแถ แตหก things EHB can do in Yarn, such as settings variables, or doing stuff conditionally:
<<๐๐๐ $๐๐๐๐๐ข ๐๐ ๐ท0>>
...
๐บ๐๐๐๐๐๐ ๐๐๐ ๐ ๐๐๐๐, ๐๐ ๐ข๐๐ ๐๐๐๐ ๐๐๐๐.
<<๐๐ $๐๐๐๐๐ข > ๐ป >>
[[ ๐ฟ๐๐ข ๐๐ | ๐ฐ ]]
<<๐๐๐๐๐>>
[[ ๐ถ๐๐ ๐ข๐๐๐ ๐๐ ๐ ๐๐๐๐๐ข, ๐๐๐. | ๐ฑ ]]

What the above produces.
On the nฬถeฬถrฬถd code end of things แดดแตสธสธ แถฆแต'หข แตสฐแต แตแถฆแตหกแต, Alec H. has also made things nice for us; instead of assuming how dialogue is displayed, or how variables are saved, the Unity/Yarn interface (aptly named YarnSpinner) leaves that all to the programmer, instead focusing on letting you know that you should display some line or save some variable, when fed a Yarn-produced text file.
So Yarn and YarnSpinner solve the problem of iteration; EHB can write and rework dialogue paths and text on her computer, only needing to update the respective Yarn file contents. No programming or mapping work involved.
But like I said above, YarnSpinner leaves to me the tasks of
- displaying text and options
- internally managing variables
Saving a variable wasn't too hard, since there's no visual aspect to it; I might cover that some other time (wink wink patron polls).
Displaying a line, however, was more complicated: the intended effect was to have the text be typed out one letter at a time, rather than popping into existence.

Like that.
The simplest way to achieve this effect is to display a larger chunk of the line every few hundred milliseconds. This works well enough, but a detail can make the text unpleasant to read, specifically when the text wraps around mid-word.
This makes your eyes start to read out a word on a line, only to have it (still half-formed) disappear, and reappear half written on the next line.
There's no obvious way to deal with this if you're feeding the text letter by letter to the textbox. If, for example, you're typing out the word
๐๐๐ ๐๐๐๐๐๐๐๐๐๐๐๐๐๐
Sesquipedalian, sษskwษชpษชหdeษชlษชษn, adjective, formal, (of a word) polysyllabic; long.
on a box yea big
<code><code><code><code><code>+---------------+
| |
| |
+---------------+
</code></code></code></code></code>
the textbox will have no way of knowing that ๐๐๐๐๐๐๐๐๐๐๐ isn't the final word
although I'm sure any reasonable human would be suspicious
and will show that on a single line, before wrapping the whole word at ๐๐๐.
<code><code><code><code><code>+---------------+ +---------------+
|The Sasquipedal| |The |
| | ----> |Sesquipedalian |
+---------------+ +---------------+
</code></code></code></code></code>
Luckily, this can be worked around with the following trick (or algorithm, if you're feeling fancy):
- 1 - Paste all the text you want to type out into the box. This
allows the box to properly wrap any overly long word, as well as auto size
the font if needed. - 2 - Hide every letter.
- 3 - Instead of adding letters to the text, show one more letter every hundred milliseconds.
Problem solved! Save it, ship it, done.

๐ Advanced Dialogue Box
Right, so there's still a couple of unaddressed things, namely:
- Text effects
- Speaking label
- Character portraits (which we internally refer to as Mugshots)
The last one is too big a topic for this article, but I would love to cover it some other time!
Following the same philosophy as before, all of these things should be controllable from the Yarn text; EHB shouldn't have to ask me to pause .5 seconds more between words!
A nice way to code this into text is with (sweet, sweet, 2004 forum) BBCodes. You might have seen these before!
`๐๐๐๐ ๐๐๐ก๐ ๐๐๐๐๐ ๐๐๐๐ ๐๐๐ [๐๐๐ = ๐๐๐๐๐, ๐๐๐๐๐] ๐ธ๐๐๐๐๐ ๐๐ ๐๐๐ [/๐๐๐] ๐๐๐ ๐๐๐ ๐๐๐ก๐ ๐๐๐๐๐๐๐๐๐ ๐๐๐๐
Using this format, I can define commands at EHB's request, whose parameters can be fine tuned inside the text. Take, for example:
๐พ๐๐๐ ๐๐ [๐๐๐๐ = ๐ท.๐ธ] ๐ผ๐๐ ๐๐๐๐๐๐๐๐๐. [๐ ๐๐๐] [๐๐๐๐๐ = ๐ท0] ๐ฝ๐๐๐? [/๐๐๐๐๐] [๐๐๐๐]
with this this we get

This tag solution also elegantly solves the speaking label issue; just code it into a tag!
[๐๐๐๐๐๐๐๐ = ๐ด๐๐๐๐] ๐ท๐๐๐๐ ๐ข๐๐ ๐๐๐๐ ๐๐ ๐ด๐๐๐๐
This sets the label image to
resulting in

There's one final hiccup to be solved here: what do we display on the screen, and when do we "execute" the tags?
It's actually already solved in the gifs above, since I've already gone through this process ยฏ\_(ใ)_/ยฏ
Well, we certainly don't want to display the tags inside the dialogue, so we might consider stripping them from the text before following steps 1 through 3 mentioned before:
๐พ๐๐๐ ๐๐ [๐๐๐๐ = ๐ท.๐ธ] ๐ผ๐๐ ๐๐๐๐๐๐๐๐๐. [๐ ๐๐๐] [๐๐๐๐๐ = ๐ท0] ๐ฝ๐๐๐? [/๐๐๐๐๐] [๐๐๐๐]
becomes
<code><code><code><code><code>[wait]
[Wait = 1.2] | [Shake = 10]
| | | [/Shake]
| | | | [Wait]
| | | | |
V V V V V
Omae Wa Mou Shindeiru. Nani?
</code></code></code></code></code>
But what if a tag changes the text? Say that EHB and I had agreed to define a [๐๐๐๐๐] tag, which clears the textbox.
If EHB writes
๐พ๐๐๐ ๐ ๐ ๐๐๐ ๐๐๐๐๐๐๐๐๐ [๐๐๐๐๐] ๐ฝ๐๐๐?
she probably expects to still see Nani typed out!
I could, of course, try to carefully define the tag to only delete the previous text, but this is prone to error, since I would also have to be careful to not misalign the remaining tags with the remaining text.
A more realistic and legitimate case would be if you're autosizing the text, or centering it; some tags you'd want to skip them and add the text in front to the textbox,
whereas with, for example, [๐๐๐๐๐], adding the text that follows would cause problems. Inconsistency is error prone, so you don't want to be handling some tags differently than others.
Instead, we can adopt a slightly different strategy.
Rather than stripping the tags and saving their position, we only parse text up until a tag. So, in this case:
๐พ๐๐๐ ๐๐ [๐๐๐๐ = ๐ท.๐ธ] ๐ผ๐๐ ๐๐๐๐๐๐๐๐๐. [๐ ๐๐๐] [๐๐๐๐๐ = ๐ท0] ๐ฝ๐๐๐? [/๐๐๐๐๐] [๐๐๐๐]
becomes, after the first step,
๐พ๐๐๐ ๐๐ (stopped at [wait = 1.2])
Then, we type out that text, using the previously discussed technique. Once that's done, we evaluate (and skip) the tag, that can freely modify just about anything; in this case, we just don't do anything for 1.2 seconds.
This can be repeated until reaching the end of the text:
๐พ๐๐๐ ๐๐ ๐ผ๐๐ ๐๐๐๐๐๐๐๐๐. (stopped at [wait] โ for player input)
๐พ๐๐๐ ๐๐ ๐ผ๐๐ ๐๐๐๐๐๐๐๐๐. (stopped at [shake = 10] โ modifies the text)
The thing we use to display text in Unity doesn't recognize [๐๐๐๐๐] but it can be made to work with <๐๐๐๐๐>, which you might've seen if you ever opened the HTML inspector in your browser by accident
`๐พ๐๐๐ ๐๐ ๐ผ๐๐ ๐๐๐๐๐๐๐๐๐. <๐๐๐ข๐๐="๐๐๐๐๐"> ๐ฝ๐๐๐? (stopped at [/Shake] โ modifies the text)
`๐พ๐๐๐ ๐๐ ๐ผ๐๐ ๐๐๐๐๐๐๐๐๐. <๐๐๐ข๐๐="๐๐๐๐๐"> ๐ฝ๐๐๐? </๐๐๐ข๐๐> (stopped at [wait] โ wait for player input)
This makes for a very powerful system! I don't really need to worry about what I'm doing to the text, and have the guarantee that the tag will only be executed once the text typing has reached that point where the tag is!
It's such a nice day today!
[wait]
Too bad I'll have to waste it working again...
[wait]
Sigh...
[wait]
Oh, well! I've just got to push through!
</code></code></code></code></code>

We're not really making extensive use of the tags here, but the parsing of the [wait] tag depends on all of the above discussed!
So, with that, our dialogue system is complete! EHB can write and tweak her heart out, and not have to talk to me once*.
I hope you enjoyed reading a bit about how dialogue works behind the scenes! I realize this kind of nerdy stuff might not be for everyone, but if you have a programmer bone in you, or are just curious about all the things that go into making a game, hopefully this will have given you some idea what's going on behind LGTS.
Let us know if you want to hear the rest of the story (especially regarding mugshots)!
โ Miguel
*A big disadvantage, according to some.