July 2nd, 2018 (back)

RAPTR - Dialog and Decision System

Dialog System

I want the dialog of Raptr to be both fun and require decision making from the player. I also want the decisions to impact the game later on. For example, certain decisions make different parts of the levels appear or a helper item. I think I have come up with a flexible system to do that.

In this system we describe conversations and trees as nested TOML objects. Let's first look at an example!

And, now, let's break it down. The dialog system is broken into four components.

  • Font System
  • Face Sprites
  • Dialog System
  • Input System

Font System

We use TOML to describe our fonts. It's pretty straight-forward. These create aliases that we can use in the game:

[[font]]
name = "default"
path = "munro_small.ttf"

[[font]]
name = "cursive"
path = "americancursive.ttf"

[[font]]
name = "bubbly"
path = "04b.ttf"
auto dialog = Dialog::from_toml(game_path.from_root("dialog/demo/dialog.toml"));
dialog->attach_controller(controllers.begin()->second);
dialog->start();
    

Internally, we use SDL_TTF to represent the fonts. I am sure there are many libraries to wrap fonts. My "Text" class is also simple:

class Text {
 public:
  std::shared_ptr<SDL_Surface> surface;
  std::shared_ptr<SDL_Texture> texture;
  SDL_Rect bbox;
  bool allocate(std::shared_ptr<Renderer>& renderer);

 public:
   static std::shared_ptr<Text> create(const FileInfo& game_root,
                                       const std::string& font,
                                       const std::string& text,
                                       int32_t size,
                                       const SDL_Color& fg,
                                       int32_t max_width = 400);
};

Face Sprites

Next, we use Aseprite to make our facial animations. We tag them with the emotion they represent.

Now, those can be refernced in the next section.

Dialog System

The dialog system is a nested TOML document that has a few required and optional keywords. Here is the dialog for the example.

[1]
speaker = "raptor-lady"
expression = "Afraid"
name = "Trapped Dinosaur"
text = "You, Mr. Raptor. Please, I am trapped, help me!"

    [1.1]
    evil_requirement = 10
    name = "Mr. Raptor"
    expression = "Angry"
    key = "help_dinosaur_1"
    value = "evil"
    speaker = "raptor"
    button = "Uh, excuse me?"
    text = "It's Emperor RAPTR. Get yourself out."

        [1.1.1]
        name = "Likely Dead Dinosaur"
        expression = "Afraid"
        speaker = "raptor-lady"
        text = "No, wait. I am sorry! Emperor, sir, help me!"

            [1.1.1.1]
            name = "Emperor RAPTR"
            expression = "Angry"
            speaker = "raptor"
            text = "lol, bye."

    [1.2]
    name = "Raptr"
    expression = "Happy"
    key = "help_dinosaur_1"
    value = "wholesome"
    speaker = "raptor"
    button = "What happened?!"
    text = "Of course! Let me give you a hand."
    trigger = "save_dinosaur_1"

        [1.2.1]
        name = "Trapped Dinosaur"
        expression = "Happy"
        speaker = "raptor-lady"
        text = "Thank goodness. Those humans put me here."

            [1.2.1.1]
            name = "Raptr"
            expression = "Happy"
            evil_requirement = 1
            key = "hate_humans_1"
            speaker = "raptor"
            value = "evil"
            button = "(push stomach out and pat on its sides)"
            text = "Don't worry, I ate like 20 on my way here, so even?"

                [1.2.1.1.1]
                name = "Seductively Trapped Dinosaur"
                expression = "Happy"
                speaker = "raptor-lady"
                text = "That's actually kind of hot."

            [1.2.1.2]
            name = "Raptr"
            expression = "Sad"
            speaker = "raptor"
            key = "hate_humans_1"
            value = "wholesome"
            button = "(show a sense of sadness and confusion)"
            text = "They don't understand us yet."

                [1.2.1.2.1]
                name = "Trapped Dinosaur"
                expression = "Angry"
                speaker = "raptor-lady"
                text = "Hmph."

The way it works is:

  1. the speaker is the originating sprite
  2. expressions reference the same expression in the animation
  3. A key/value is set if this piece of the dialog is accessed
  4. A button is set if this is a dialog to be chosen
  5. A trigger is set if this dialog causes something else in the map to happen

Coding it up

This thing is a bit of a beast as far as the rest of the code-base goes. The actual interface itself is straight-forward. You load a dialog file, attach a controller, and then go.

  auto dialog = Dialog::from_toml(game_path.from_root("dialog/demo/dialog.toml"));
dialog->attach_controller(controllers.begin()->second);
dialog->start();