Home Artists Posts Import Register

Content

 

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 toresulting 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!

<code><code><code><code><code>
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.

Comments

Anonymous

How exactly did you guys program the image for the dialogue box, character portraits, and position for text? As well as the animated set of grapes? (Thanks for the laughs from your example text. "you are already dead" never gets old.)

astralshift

Hi! We're using Unity as a starting point, so (thankfully) I don't have to take care of the bare metal aspects of rendering the images. Regarding their placement, this is done through the Unity Editor, particularly with prefabs. This bit is probably only clear if you actually use Unity, but for practical purposes: we place the images there by hand. This is not the case for the moving mugshots, but that's enough material for its own article. Miguel

Anonymous

I really love this !! is genius in its own !! I do wonder how you incorporate the mouth movement and how that is made OvO/ this is awesome!!

astralshift

Hi! Thank you! 😊 It makes me really happy that this kind of work is appreciated. Regarding your question: the mouth moving is part of the whole mugshot character system, but the gist of it is that whenever a new character is shown (step 3 in the "algorithm") I test whether it's alphanumeric (number or letter) or not. If it is, I move the mouth once (starting from whatever frame the animation is currently at). This might be slightly confusing without a more thorough explanation and some gifs, but let me know and I'll pester the team to let me write another article on the mugshot system! 😉 - Miguel