Interactive dialogue – part 3

Wow this has been really some time off the game. We’ve actually run to the point of doing nothing for months, because after finishing a basic game engine – and being quite content with being able to make a prototype game to test its neat features – we’re quite daunted by the fact that we somehow have to start making lots and lots of graphics, sounds, etc and therefore have to start working on the more boring stuff of building a good tool for putting in these game assets, which should prevent pulling out hairs every time you want to test e.g. changing a parallax layer or a few altered pixels in a sprite sheet.

Anyway for the interactive dialogue, which this post is about, this tool should not be necessary, since the input format should be easy enough to edit and understand without needing a separate tool. Check out the previous two posts on this topic for some context on this.

Ok before I’ll wrap up interactive dialogue, let’s just say: we still love to finish this game before the sun explodes, however it’ll take some aligning of stars before we’re ready to take it on afresh. Until this happens however, we’re still interested in doing the occasional tech update here, since we haven’t quite covered everything there is to say about our game engine.

Enough chatter, let’s finish up what there is to say about the dialogue engine by getting into:

  • How to parse the YAML dialogue files into a model the dialogue engine can handle
  • How the dialogue engine is built up
  • How the dialogue is put up on the screen

~ Loading & parsing ~

Before the dialogue engine can do anything, the dialogue files have to be loaded and parsed into human understandable objects. Here’s the first bit of loading code (contained within DialogueUtil) the DialogueEngine calls whenever it needs to start a dialogue for a certain character:

public static function loadDialogue(actorId: String, roomName:String):Dialogue {
  var data:AnyObjectMap = Yaml.parse( Luxe.resources.text("assets/game/dialogue/" + actorId + ".yaml"    ).asset.text);
  var d:Dialogue = new Dialogue(actorId);
  var blocks:Map<String, DialogueBlock> = new Map<String, DialogueBlock>();

//each k is a DialogueBlock, make sure to distinguish between a hero block and another block
  for(k in data.keys()) {
    var commands = parseCommandArray(data.get(k), null);
    var block:DialogueBlock = new DialogueBlock(

      k,isHeroDecisionBlock(commands)

    );
    block.commands = commands;
    blocks.set(k, block);
  }
  d.blocks = blocks;
  return d;
}

As mentioned in the previous post, you can see that each dialogue file represents all of the dialogue of a single game character (actor). What this function basically does is that it returns a Dialogue, which in turn contains a number of DialogueBlocks, which in turn each contain a number of DialogueCommands. More on the underlying model in a bit.

As you can see as well, each DialogueBlock either represents a ‘hero decision’ or not. Here’s the ugly (I actually left the comment saying it is ugly in, so you are super aware of the fact that it ain’t pretty) function that determines whether a block is a ‘hero decision’ block:

//keilelijk maar ja nou en!?

private static function isHeroDecisionBlock(commands:Array<DialogueCommand>):Bool {
  for(c in commands) {
    if(Std.is(c, ActorSpeakCommand)) {
      var asc = cast(c);
      if(asc.optionId != -1) {
        return true;
      }
    }
  }
  return false;
}

It basically looks if the contained DialogueCommand contains an option ID, which  represents the choice number of a dialogue option.

Well what about the actual function (parseCommandArray) that parses the YAML data structure and puts everything into the dialogue model? It’s a bit too much to go through it in this post, but you can find it in the GitHub repository I put up which contains all of the dialogue related code. You’ll find the link at the bottom of this post.

~ The model ~

As mentioned, the model that powers the dialogue engine is made up of the following objects:

Dialogue

A dialogue contains all of the dialogue logic the hero can have with a certain NPC regardless of location. In code a dialogue is basically a hash/map/dict containing a bunch of DialogueBlocks, which have been loaded by the aforementioned parsing functions:

class Dialogue {

  public var id:String;
  public var blocks(default, default):Map<String, DialogueBlock>;

  public function new(id:String) {
    this.id = id;
  }

}

DialogueBlock 

A DialogueBlock basically groups a number of related dialogue phrases or actions, all types of DialogueCommands, by topic. It has a human readable ID, which should reflect this topic, and can be conveniently referred to, for execution, after a certain DialogueCommand has finished.

class DialogueBlock {

  public var blockId:String;
  public var isHeroDecision(default, default):Bool;
  public var commands(default, default):Array<DialogueCommand> = new Array<DialogueCommand>();

  public function new(blockId:String, isHeroDecision:Bool) {
    this.blockId = blockId;
    this.isHeroDecision = isHeroDecision;
  }

}

DialogueCommand

A DialogueCommand is the abstract concept that reflects any action within a DialogueBlock. In code the DialogueCommand is an abstract class and currently has four different implementations, namely ActorSpeakCommand, BlockRefCommand, ExecCommand, SetGameStateCommand.

Most importantly, each implementation should implement the toLuaString() function. This function basically maps the command to a piece of Lua script that in turn will call the game engine to do some stuff.

Here’s the abstract DialogueCommand class, followed by the ActorSpeakCommand class that extends it.

class DialogueCommand {

  public var precondition(default, default):String;
  public var optionId(default, default):Int;
  public var optionIndex(default, default):Int;

  public function new(precondition:String, optionId:Int = -1, optionIndex = -1) {
    this.precondition = precondition;
    this.optionId = optionId;
    this.optionIndex = optionIndex;
  }

  public function run():Void {

  }

  public function toLuaString():String {
    return null;
  }

}

class ActorSpeakCommand extends DialogueCommand {

  public var actorId:String;
  public var text:String;

  public function new(precondition:String, actorId:String, text:String, optionId:Int=-1, optionIndex=-1) {
    super(precondition, optionId, optionIndex);
    this.actorId = actorId;
    this.text = text;
  }

  override public function toLuaString():String {
    var s:String = "\tactorSays(\""+actorId+"\", {\""+text+"\"})\n";
    s += "\twaitSignal(\"finishedspeaking_"+actorId+"\")";
    return s;
  }
}

The optionId and optionIndex are used to directly reference the command in case it is related to a choice the player makes in the dialogue HUD (so if the command is within a DialogueBlock that is a ‘hero decision block’).

~ The engine ~

The DialogueEngine is basically capable of executing DialogueBlocks and DialogueCommands. In order to start a dialogue, the scripting engine or game engine simply has to call the runDialogue function and supply it with an actor ID:

public function runDialogue(actorId:String) {

  if(currentDialogue == null) {
    currentDialogue = DialogueUtil.loadDialogue(actorId, null);
  }

  if(currentDialogue != null) {
    playState.currentGameState = Util.DIALOGUESTATE;
    runDialogueBlock(currentDialogue.blocks.get('first_encounter'));
  }
}

What follows is that the correct YAML file is loaded – based on the actor ID – and subsequently the game state is set to DIALOGUESTATE, which is needed to make sure that the game engine e.g. knows that it should hide the regular interface elements to make room for the DialogueHUD, which needs to be displayed whenever the dialogue runs into a choice for the player (more on this later). Lastly the dialogue is started by executing the mandatory DialogueBlock with the label/ID ‘first_encounter’ (see previous post).

You should check out the runDialogueBlock function on GitHub. Basically it checks whether it should show the dialogueHUD (and populate it with the correct options) and give the player a choice, or it simply translates the DialogueCommands to Lua code and executes this, so e.g. an NPC will say something and/or a cutscene will start, etc.

~ How the engine is integrated ~

The dialogue engine is simply a package that is part of the game engine and which has a specific goal & task: make it possible to have the game characters engage in conversation. More specifically, the dialogue engine exposes a number of functions that can be used in the Lua scripting engine. How this scripting engine works is something for another post. In the meantime however, here are the functions that your scripting guy can use now:

runDialogue(actorName)

waitSignal('finished_dialogue)

The first one simply makes a certain dialogue start. Of course the actors involved must be present in the same room for it to work.

The second simply blocks the script until the game engine sends the ‘finished_dialogue’ signal, but how all this exactly works is for the next time.

~ Dialogue HUD ~

Finally the only thing that requires some attention is the DialogueHUD that draws the options for the player choices on the screen.

schermafbeelding-2016-10-13-om-20-43-22

Well the only thing interesting to say is that the DialogueHUD gets called by the dialogue engine with the options it should show. Whenever the user selects an option, the HUD calls the dialogue engine to execute all of the DialogueCommands that are related to the selected option’s ID.

~ Code! ~

Probably way easier than reading this post is actually checking out the code itself in GitHub. I added lots of comments, so it shouldn’t be that hard to figure out. Also please note that you cannot simply check-out and run the code, since you would require the rest of our game engine, which we don’t have publicly online. I did keep all the imports and code that refers to our game engine, so you can better understand how it would work.

Alright, I hope this stuff makes sense to you. Please feel free to ask any question in the comments!

Until we meet again (and most likely will go into the Lua integration)

 

 

 

7 thoughts on “Interactive dialogue – part 3

  1. I enjoy reading about the way you’re implementing the dialog system and a point and click adventure overall, as I took a different approach to both of that. But it’s always interesting and helpful to know how other people do it and to find something I can change in my Engine.

    I actually wrote a whole script engine for the adventure game I’m making (C++ & Allegro as a low-level framework). This, whilst it is of course a lot more work, gave me complete freedom in how I design every file format and what dialogues do.
    My Dialogues: (here one, where the player can choose between different dialogue options):

    addDialogChoice str_ron_talk_about_job
    if §exists{player_knows_about_company} \\ For conditional options
    addDialogChoice str_ron_give_more_information_company
    end
    (…)
    startDialogChoice \\ Showing the player the options
    if $dialogchoice == str_ron_talk_about_job
    say $curchar “Where are you working Ron?!”
    say char_ron “At a company!”
    setvar bool player_knows_about_company
    end

    …I think the general concept should be clear by now.

    1. Good to hear you found it useful & thanks for some insight into your approach. It seems like your dialogues are part of your script files (same syntax)? Or do you store them separately from your other scripting code?

      Since you’re also writing a script engine: we’re writing a post about ours (which enables scripting with Lua on our game engine). Curious to see how you can compare that to yours.

      It will probably take a while again before we’ll actually post it though

      1. Yes, I decided to treat dialogues as normal scripts (and they are in fact nothing more in my engine), because it’s simpler in many ways (at least for me) and it allows dialogues to interact with the game in any way (change character costume, move characters, give objects etc.), albeit in a more technical syntax.

        And since I’m apparently not able to answer on the actual lua post:
        My first idea back when I started to write my engine was to do the game logic with lua, but couldn’t get it to run in the way I wanted (I found it to difficult to communicate between C++ and Luca) and instead wrote a new script engine.

        But the way our scripts are structured is quite similar, I could nearly run your script in my engine (after exchanging the function names and changing the function syntax of course…).
        A difference is in how we treat commands that take a certain time (like actorsays in your script):
        A “ScriptCommand” (the internal class name in my engine) can have a “length” parameter that determines how long the engine waits until the next command is processed, so that I don’t need a waitSignal-command. This is a trivial thing to do when writing the whole interpreter from the ground up, but prevents me from forgetting a “waitSignal” and is simpler to write in scripts.

        1. Writing and maintaining you own script engine must be time a consuming exercise! But I guess it comes with great flexibility and you can adjust it the way you want.
          I like the idea of ‘duration’ as a parameter for to the engine. We might use that idea and build that in our Lua implementation.

          We fixed the comments in our Lua post. Thanks for pointing that out!

          1. The most time consuming part was getting everything right (like order of operators and brackets etc.), making new commands is fairly simple (although my engine is still messy in some parts).
            Adding new commands is nearly as simple as it is in your solution (atleast I hope so).

  2. This blog is a gem, I am extremely happy that I found it! Never thought someone is blogging about adventure game development. I just want to express my gratitude for posting these very useful and interesting content. I hope you will continue it, you are really giving incredible value for point and click developers. Thank you so much!

    1. Thanks! Curious to hear what you’re working on?

      The next post on scripting should be up in a not too distant future

      Getting back to development will take a while longer, since we need to spend some serious time on creating tools for optimising the proces of putting in game content, which is a little bit tedious to get into after making the actual game engine.

      Until that time we do have enough to write about, but not actively into the development of the game, you can probably appreciate that updates won’t be pouring from this page.

Leave a Reply to Mic Uurloon Cancel reply