Interactive dialogue – part 2

Having extended the holiday slumber for way too long, let’s continue with interactive dialogue.

This time I’ll go through an input (YAML) file that shows all of the currently available interactive dialogue features, which I’ll try to explain in detail.

In the next (and last) post on this topic I’ll show you the layout of classes that make up our dialogue model & dialogue engine and finally an embarrassing parsing function that parses the input file and maps its contents onto the dialogue model.

Now, let’s have a look at the example:

first_encounter:
  - judy:
    - {if : judy.location==drewoffice}
    - What's up Drew?
    - Those guys just called and are ten minutes away!
    - You better make sure everything is arranged in meeting room G!
    - :main
  - judy:
    - {if: judy.location==mainhall}
    - So you're all set?!
    - Please tell me you've got it all arranged now
    - __nervous_quirk
    - :main

question:
  - judy:
    - "Not now Drew, just make sure stuff is arranged!"
    - They can be here any moment now!
    - __nervous_quirk
    - {if: judy.told_about_kate==false}
    - Oh and tell Kate she has to arrange the doorman!
    - $judy.told_about_kate=true
    - hero: "Alright alright! No worries Judes!"
  - judy:
    - {if: judy.location==drewoffice}
    - __judy_leaves
    - $judy.location=mainhall
    - :exit
  - judy:
    - {if: judy.location==mainhall}
    - :exit

impatient:
  - judy: Well isn't that nice... Now please HURRY UP!
  - hero: Just testing if we were in perfect sync. As you were!
  - :annoyed

verify_judy:
  - judy: "So tell me, what is it?"
  - :verify_drew

fennimore:
  - judy: "No he isn't, are you trying to trick him into doing your work? Now get going PLEASE!"
  - hero: Good to see we're tight and remained on the same level! See you around!
  - :exit

annoyed:
  - judy:
    - {if: judy.location==drewoffice}
    - "*Mumbles* one day I'm gonna..."
    - __judy_leaves
    - $judy.location=mainhall
    - :exit
  - judy:
    - {if: judy.location==mainhall}
    - "*Mumbles* This cannot continue forever..."
    - :exit

# ----------------------------HERO DIALOGUE----------------------------
main:
  - 1: [{if: once},"I was just going to tell you it's all Ay-Oh-Kay!", __grins, :impatient]
  - 1: [{if: once},"I was just going to tell you: the deed is done!", __grins, :impatient]
  - 2: ["Well that's exactly what I wanted to talk about.", "Do you have a minute?", :question]
  - 3: [{if: once},"So this is not about finally buying me lunch?", :question]
  - 4: [{if: judy.location==mainhall}, "The meeting made me think of something I wanted to verify with you", :verify_judy]

verify_drew:
  - 1: [{if: talked_to_fennimore==true}, "Well it's Fennimore, isn't he also supposed to help out?", :fennimore]
  - 2: ["Nah, forget about it. I just wanted to bring the intrigue back into our relationship", :annoyed]

Note that currently each dialogue file contains all of the dialogue of a single (non-playing) character regardless of what location he or she is at in the game (see images below). If at some point in the creation of the game this will result in files that will become too big to handle conveniently, we could change this easily.

Schermafbeelding 2016-01-25 om 21.30.50

Schermafbeelding 2016-01-25 om 21.33.03

Alright, now let me address each feature first and then show an animated GIF (near the end) to recover from the dullness of text: we’re talkin’ video games here! Oh and I won’t be explaining any YAML syntax, so you best check out some specs here.

First encounter

At the top of each dialogue file it’s necessary to put a dialogue block named first_encounter.

first_encounter:
  - judy:
    - {if : judy.location==drewoffice}
    - What's up Drew?
    - Those guys just called and are ten minutes away!
    - You better make sure everything is arranged in meeting room G!
    - :main

So whenever you engage a dialogue with the NPC, the first_encounter block is executed before everything else.

Dialogue block

As you can see the file content is split up in blocks. On what basis does one define a block?

Generally I find it useful to define a block for each topic of discussion. When getting back to a dialogue file, I find this grouping tactic makes it quite easy to get back into whatever I had in mind when first writing it.

There’s one more high level thing to know about dialogue blocks: there are decision blocks and regular blocks (by lack of a better designation…).

Decision blocks represent the moments in a dialogue where the hero has a number of choices to make (I’ll describe this type of block at the end of this post).

Regular blocks can be basically any other part of the dialogue, involving one or more NPCs and possibly also the hero.

Alright, let’s go into the different available mechanics & features that are available for both type of blocks. Let’s start with the one that each block must define. Without it, the dialogue engine will get stuck.

Block reference 

Parts starting with a colon, such as :annoyed, :verify_drew or :exit is called a block reference and can be compared with the goto 10 or goto 20 whatever we had in BASIC.

:exit is a reserved reference, which, as you most likely figured out, ends the dialogue.

So what I described earlier as the flow of the interactive dialogue can be controlled – on the highest level – with blocks and block references. On a more detailed level this flow can be controlled with conditions.

Conditions

There are currently two types of these:

  1. precondition in the form of an expression, e.g. {if: x==y} or {if: actor.location==scary_forest}.
  2. A preset behavioral pattern, currently only the value once is supported, which indicates that subsequent statements will be executed only once.

Regarding the preconditions, currently only the following expressions are supported:

  • Comparison between two game state variables
  • Comparison between an actor (object) and a game state variable
  • In both cases only the following operators are supported: == and !=

We call any object that can be loaded into a room an actor, which means NPCs are also objects, just as much as any old ‘precariously positioned bucket of acid’.

Finally it’s important to know: whenever a condition is put in place, it means that all lines after it, within the same sub section [one or more statements/lines that are listed under a certain actor] of a block, are executed if the condition is met or skipped if the condition is not met. The following snippet from the example illustrates this best:

  - judy:
    - {if: judy.location==drewoffice}
    - __judy_leaves
    - $judy.location=mainhall
    - :exit
  - judy:
    - {if: judy.location==mainhall}
    - :exit

So here you can see that if the Judy is in Drew’s office, the first condition succeeds and respectively the cutscene judy_leaves is executed, the actor’s position is updated and finally the dialogue is exited.

If Judy would be in the main hall instead, the first condition would not be met and all of the aforementioned lines would not have been executed. Instead, the dialogue engine will go to the next sub section, which condition is met, etc.

Setting game state or actor properties

So next to defining conditions based on the game state or actor properties, it’s also possible to update any assigned value, by addressing the variable name with a preceding dollar sign and subsequently using the = operator to assign a new value:

$judy.location=mainhall

Next to the assignment operator, also ++ and — is supported for increasing or decreasing integer values by 1:

$fennimore.annoyancefactor++

Typically the last example is useful for keeping track of how many times you’ve asked the same question to some NPC, having the NPC crack down after 10 times – by defining a condition. I mean who didn’t like clicking each unit a million times on Warcraft, waiting for them to change their response?

Scripting hook

To get that dramatic freedom adventure game writers all crave, it’s necessary to provide a mechanism for inserting cutscenes spiced up with the occasional special animation. The way it’s done in our case is simply inserting a ‘scripting hook’, i.e. a reference to a piece of (lua) script, e.g.:

__judy_leaves

Currently the format for this is the name of the lua function prefixed with two underscores. To entertain you while waiting for that lua/scripting post, here’s the function referred to:

function judy_leaves()
    startCutScene(true)
    actorGotoXY("judy", 0, 379)
    waitSignal("arrived_judy")
    actorRemove("judy")
    setGameState("judy_left_drewoffice", "true")
    stopCutScene()
end

Dialogue lines

It seems I went into the fancy bits first, without mentioning how to define which actor says what. Let’s go already:

The nice thing of YAML is, is that you don’t have to wrap a piece of text in apostrophes (“), so just writing text as you like works fine, unless you start using characters that will create an ambiguous situation for the parser. Just don’t do that.

Moreover, since you can add a whole list of statements (a.k.a. sub section) to a certain actor, it is possible to use multiple lines to split up sentences that are too long to say in one go. This way in the game world, the character will speak first one line, then the other, giving the player more time to read as well as avoiding the screen to be too small to fit the text.

Decision blocks

Finally something needs to be said about decision blocks. You can recognize a decision block by the use of numbers, instead of actor names, to group a list of statements:

main:
  - 1: [{if: once},"I was just going to tell you it's all Ay-Oh-Kay!", __grins, :impatient]
  - 1: [{if: once},"I was just going to tell you: the deed is done!", __grins, :impatient]
  - 2: ["Well that's exactly what I wanted to talk about.", "Do you have a minute?", :question]
  - 3: [{if: once},"So this is not about finally buying me lunch?", :question]
  - 4: [{if: judy.location==mainhall}, "The meeting made me think of something I wanted to verify with you", :verify_judy]

So obviously in decision blocks each option number represents a possible answer for the player to choose. The options will appear in the game in the same order, provided of course that its condition (if any) is met.

talk_to_judy
The first bits are sped up by pressing ESC. After the option is chosen, the dialogue runs normal speed and, before exiting, a cutscene is played

In this example you can also see the use of an option group (see part 1) since I’ve defined two answers for option 1 both with the special ‘once condition’. defined. Both variants can only be asked once. In that case option 2 will become the first option in the game.

To finish the list of options: option 2 will always be available, since it has no condition set; option 3 will be available only once; and option 4 will only be available if the character Judy is in the main hall location.

That’s it for now

Well it seems I’ve covered most of the things worth mentioning. Once I’ve finished my next post, I plan to put the dialogue engine code in GitHub so you can check it out and play with it directly.

In case you have any questions or anything else, please let us know what you think in the comments.

 

 

11 thoughts on “Interactive dialogue – part 2

  1. These are great posts.

    I’d be curious on the rationale of using double underscore since it creates inconsistency, and affects the “readability” reasoning for yaml.

    In other words: I feel like __judy_leaves is weaker than the more obvious judy_leaves() – the parsing is as easy, it’s more internally consistent, aligns better with the goals described and is clearer to reading the dialog text without any reference as to what is happening. On clarity/legibility, for people with no understanding of the system – __this_is_unintuitive_for_a_function_call – both for programmers and content creators alike.

    Good work, looking forward to more.

    1. Hi Sven,

      thanks for your useful comment. The double underscore is indeed a bit strange. With this format I tried to initially target writers who are not familiar with code.

      Therefore I tried to make the format look like normal text as much as possible and as little as possible as programming code.

      I guess therefore I ditched the function() notation, which of course looks like code.

      However, I think you’re right: just using judy_leaves() would be equally easy for a writer and would be more internally consistent.

      When I get back to another iteration, I’ll probably change this 🙂

      + of course good to hear you appreciate these posts!

      The next post will be Pathfinding part 2 (by MicUurloon), then I’ll write part 3 on dialogue.

  2. Hi,

    This is a great post. I loved reading your path finding posts as well.

    Are you going to publish the dialog parsing code?

    Thanks!

  3. Thanks!

    I’m thinking of putting up part 3 of the interactive dialogue somewhere in the next month or so (holidays are coming up). Accompanying that post I’ll put up a link to a code repository.

      1. Good to hear you’re finding it useful! I’m still busy writing part 3 of this post. It’s quite long, but I’m getting there. I don’t have much time, but will finish it hopefully within the days to come.

        Cheers

  4. Hey Guys,

    Thanks heaps for your articles, they are really informative, thanks a lot for taking out the time

    Right now I am looking at implementing a font rendering engine similar to what you have done, and the way SCUMM games like monkey island 1 and indy 4 have worked.

    I was wondering if you guys are going to do an article on that with your insights or if you could point me to any handy resources?

    1. (I guess in particular some of the problems I am thinking of is how to format the text when its close to the edge of the screen and you have to decide to wrap it into multiple lines in a box or to just shift the single line over so it fits on screen)

      1. Hi Dom,

        thanks & good question. The fact of the matter is: so far I/we haven’t actually gotten around to implementing this properly, because it is indeed quite challenging to do neatly.

        So first there are – like you also said – the edges and corners of the screen you have to think about. Secondly there are also the other characters on the screen one’d rather avoid overlapping with text (in some situations).

        Then there might also be a problem with braking off really long words on multiple lines (this could probably avoided in most cases, but you never know)

        Anyway there is actually a good chance to get a nice answer using the upcoming “Friday Questions” round on the Thimbleweed park blog. In their last podcast they mentioned their next podcast will cover questions from blog visitors, so keep an eye out on their blog (this Friday I guess) to enter your question there. (As far as I know no one ever asked it there before)

        Lastly, whenever we’ve implemented something for this we’ll write up a post about it as part 4 of the interactive dialogue posts.

        Cheers

Leave a Reply

Your email address will not be published.