[For a couple of years now, I’ve reserved the first Tuesday of the month for a review of a book on writing or game design that might be of interest to IF folks. I’m still doing one of those in March, but it will come out the 19th, while I’m at GDC.
Instead, this post is part of a short series on Character Engine and what we’re doing at Spirit AI. I’m writing these posts with IF and interactive narrative folks in mind, but more general-audience versions of the same content are also appearing on Spirit’s Medium account. Follow us there if you’re interested in hearing regularly about what Spirit is up to.]
Knowledge and memory are a somewhat vexed area for game characters. It’s easy to think of characters who don’t remember the last fifty times you asked them the same exact lore question, or are strangely forgetful about the ways you’ve harmed them, or who aren’t equipped to answer common-sense questions about the world they live in.
So why is this a problem? Simply recalling that something has happened is not the main challenge. We can set flags; we can assign variables; we can check on quest journals to see what the player has already done. We can refer back to whatever data store is otherwise tracking world state in this game.
The hard part is building a system where
- everything important to remember is stored in a reasonably systematic way
- differences between world truth and character knowledge are handled as much as (and no more than) useful
- there’s a way to track and author for the combinations of possible state so that the NPCs always have something to say about what they remember and know
There are quite a few technical, design, and writing challenges packed into those three bullet points.
“Everything important to remember” means that we have to have some idea of what counts as important in this game world. A fact is important from a game design perspective if it affects the player’s ability to act in the world, and the mechanics that they’re going to be able to bring to bear. It’s important from a narrative perspective if it determines stakes, motives, or consequences of a given conflict or choice point. And it’s important from a systems legibility standpoint if it later allows the game to tell the player why something happened.
“…stored in a reasonably systematic way” means that we’ve anticipated how we’re going to be using that information again later, and we’ve made sure our data structure has laid the groundwork for that.
“Differences between world truth and character knowledge.” Though it’s usually easy(ish) to track what has happened in the game world, things get more complex when we want to track what individual characters know as distinct from the actual world state (“The murder weapon was a knife” vs “Bob knows the murder weapon was a knife”). It gets further complicated if we want characters to track what they think other characters know (“Bob knows that Sue knows that the murder weapon was a knife.”)
“…handled as much as (but no more than) useful.” In practice, there are very few games that do very much with characters who have ideas about the knowledge of other characters.
Where I’ve seen this done successfully, it has always been driven by the narrative design first. It’s not usually interesting for characters to be reasoning about where they last saw the cat, so there’s no reason to build a heavyweight system to store that information; it’s enough to have a much sparser model of beliefs about narratively critical information. Jon Ingold’s text adventure Make It Good does this: it’s a murder mystery where the game play is all about manipulating what the other characters know, or think they know.
“There’s a way to author to reflect all that state.” Very very broadly speaking, there are a few standard ways to approach dynamic text that reflects world state:
Have a template system that can be filled in with variables. A lot of interactive fiction tools, from Twine to Inform, would let you use this approach. It works for a lot of basic purposes, but if you’re going to be using the same template a lot in the course of the game, it soon becomes quite obvious to players that they’re dealing with something a bit boilerplate. “Curse you, [epithet for player]! I am still [emotion] about the time you [description of past crime]!” becomes less amazing if you’ve heard it enough times.
Have a generative grammar like Tracery, where a single top-level concept can randomly expand into lots of different sentence (or paragraph) formats. This is typically a super-set of the template system, and can also do some variable substitution.
Have a salience-based system that fires the most salient line given the current world state, and can fall back to defaults. This is the method used by Left4Dead and described by Elan Ruskin in this GDC talk, and borrowed by Firewatch. This approach means the developer can have one default version of a line and then lots of somewhat or very specific variations. Players are less likely to recognize a formula, but it’s more likely that the world state coverage won’t be very thorough: that is, it’s more likely that there will be lots of states the world can get into that the system isn’t designed to mention or call out.
Building Lots of Ways to Convey Information Depending on Circumstances
Character Engine’s text generation system does all of these in combination. We use a tagged generative grammar, so that every line of dialogue can be indexed with its metadata. The generative grammar aspect means that we can systematically build out variants to cover lots of possibilities. Perhaps we want to use one epithet for the player by default, and another if the speaking character is related to the player; perhaps we want to choose between “Curse you” or something stronger depending on how angry the character is.
The tagging then allows us to pick the most salient of all our generated options. Perhaps one variation of a line reflects that the character is angry and open-minded and that they’re mentioning a particular event. Another variation of the line mentions the same event, but it’s suitable to a character who is happy. The system can then select the most salient option.
After it’s done that, we expand embedded variables in the line and perform some other work, like automatically substituting pronouns to refer back to current topics of conversation.
There are additional nuances here, like the ability to mark some lines for single use only, or specify a line to be used the first time a particular grammar node is expanded. We’ve also put a good bit of work into optimizing the system for runtime performance and to support rapid authoring.
The end result: text generation tools that let authors rapidly account for a wide range of world state in a systematic way — including dialogue that adapts to the speaker’s knowledge and memory.
All of that is available in the engine now: we can take a content request for a single piece of information, or a single narrative beat, and built that out into many potential representations.
Summarizing Events and Histories
The next step, still in progress, is the ability to dynamically summarize multiple pieces of information. Imagine, for instance, that we’ve recorded several events that were important in a character’s past. Maybe those events were dynamically generated by a simulation or a procedural backstory-generation system. Maybe they came about because of the player’s actions. Either way, they weren’t pre-authored. And yet, somehow, we need to let characters talk about their pasts.
For that task, we need an additional layer of reasoning — allowing characters to pick which events are important enough or currently relevant enough to mention; deciding how much detail to go into, and which details. That reasoning then produces a compound content request (“please mention this fact and that fact”)… which can then be fed into the same text generation system as before.
The result: characters who can talk about event sequences that the author never specifically anticipated, in language that is specific to their current mood and persona.