Designing Data for Games

Designing data structures is one of the most important things we do as software developers – it informs the structure and cadence of the code that follows.

Here, I’d like to think aloud about data structures for character dialogue and quests in the game I’m making. Let’s start with dialogue, since I believe it’ll be a simpler data structure.

Dialogue is an interesting aspect of some games. Sometimes it’s very straightforward: you ask and the character, the NPC (non-player character) answers. They say the same thing every single time and don’t react to anything.

If this were the simple case, I wouldn’t need a data structure, except maybe a simple table to associate characters with their lines. If you know me, you know I’m not going for the simple case.

I like games like Star Wars: Knights of the Old Republic or Fallout 4, where you’re given many options regarding what you can say to the character, and they in turn respond to what you choose. This can lead to other options, it can trigger a quest or give you an item, it can affect how much the NPC likes you (or not), and options may even go away when you choose one!

The way I’m thinking of dialogue, it can be represented as a tree of sorts. Of course, Lua only has one data structure, the table, which serves as array, hash map, and object all in one. I’m picturing something like this so far (syntax is Lua):

dialogueTree = {
  firstOptionId = {
    optionText = "This is the first option that will be displayed to the player.",
    response = "Well that's neat!"
  },
  secondOptionId = {
    optionText = "This is the second dialogue choice, displayed alongside the first.",
    response = "Yep, I'm getting the picture."
  }
}

If there are options shown after you choose the first option, those should be nested under the first like you’d probably expect (we’ll use a real example this time):

elderDialogue = {
  start = {
    optionText = "Hi, I'm Foo, Mr. Elder.",
    response = "My father was Mr. Elder. You can call me sir.",
    options = {
      polite = {
        optionText = "Of course sir. Do you know where the secret cave is?",
        response = { -- response can be a table as well as a simple string
          text = "You're a nice kid, here's the map. Not much of a secret, really.",
          item = "thisIsAnItemId" -- this should refer to a canonical item table
        },
        options = {
          sayThanks = {
            optionText = "Thank you!",
            response = "You are very welcome, friendly stranger.",
            ending = true -- this option will end the conversation with this NPC
          },
          walkAway = {
            optionText = "[Walk away]",
            ending = true -- if you walk away there shouldn't be a response
          }
        }
      },
      rude = {
        optionText = "I could, but I'll call you Slappy!",
        response = "Well, you won't get anything from me! Go away.",
        ending = true
      }
    }
  }
}

You can see how this is starting to evolve. There are a few potential issues with this.

First, there should always be a “[walk away]” option, at least in this game. That could change later – maybe you shouldn’t be able to end a dialogue with your captor, if you’ve been imprisoned or something, but I’m not worried about that right this minute. It would certainly make the tree smaller and simpler, which would be nice, but let’s bear in mind this isn’t made for human consumption! This data will be generated by a web app.

That reminds me, we should be able to import as well as export from the web app! I should be able to pipe data back in, modify it, and get the result from the app. This is critical, otherwise I’d have to input the dialogue tree every time or save it elsewhere, which is undesirable. I don’t need a database for this app, or shouldn’t yet.

The other potential issue is the response structure: it could be a string or a table. This seems fine at first glance but requires either extreme caution at every point you plan to access the field, or a wrapper module that has a getter function for the response…and since this isn’t made to be written or read by humans, it’s not a huge deal to make it an object all the time, to make the code easier to deal with.

This is getting long, I lost my pace, and the quest data structure will be even more complex, so I’ll make a “Part 2” and link it here when it’s done.

[UPDATE: Read part 2 here]

© - 2022 · notes by Austin Pocus · Theme Simpleness Powered by Hugo ·