The Ultima series is one of the most influential RPG series of all time. It is known for open worlds, intricate plots, ethical choices as opposed to “just kill the bad guy”, and… dialogue. The dialogue of the Ultima series went from being simple one-liners to complex dialogue trees with scripted side-effects.
Ultima 4-6, as well as the two Worlds of Ultima games (which used the Ultima 6 engine), used a simple keyword-based dialogue engine.
In these games, conversing with NPCs (people) involved typing in a number of common keywords such as “name” or “job”, and entering new keywords based on their responses in order to develop the conversation. Only the first four characters were taken into consideration, so “batt” and “battle” would yield the same result. “Bye” or an empty input ends the conversation, and any unrecognised keyword results in a fixed default response.
In Ultima 4, conversations were restricted to “name”, “job”, “health”, as well as two other NPC-specific keywords. For each NPC, one keyword would also trigger a question, to which you had to answer “yes” or “no”, and the NPC would respond differently based on your answer. You can view transcripts for or interact with almost all Ultima 4 dialogues on my oldest website, Dino’s Ultima Page, to get an idea how this works.
Later games improved this dialogue engine by highlighting keywords, adding more NPC-specific keywords, allowing multiple keywords to point to the same response, and causing side effects such as the NPC giving you an item.
If we focus on the core aspects of the dialogue engine, it is really simple to build something similar in just about any programming language you like. In C#, we could use a dictionary to hold the input keywords and the matching responses:
var dialogue = new Dictionary<string, string>() { ["name"] = "My name is Tom.", ["job"] = "I chase Jerry.", ["heal"] = "I am hungry!", ["jerr"] = "Jerry the mouse!", ["hung"] = "I want to eat Jerry!", ["bye"] = "Goodbye!", ["default"] = "What do you mean?" };
We then loop until the conversation is over:
string input = null; bool done = false; while (!done) { // the rest of the code goes here }
We accept input, and then process it to make it really easy to just index the dictionary later:
Console.Write("You say: "); input = Console.ReadLine().Trim().ToLowerInvariant(); if (input.Length > 4) input = input.Substring(0, 4);
Whitespace around the input is trimmed off, and the input is converted to lowercase to match how we are storing the keywords in the dictionary’s keys. If the input is longer than 4 characters, we truncate it to the first four characters.
if (input == string.Empty) input = "bye"; if (input == "bye") done = true;
An empty input or “bye” will break out of the loop, ending the conversation.
if (dialogue.ContainsKey(input)) Console.WriteLine(dialogue[input]); else Console.WriteLine(dialogue["default"]);
The above code is the heart of the dialogue engine. It simply checks whether the input matches a known keyword. If it does, it returns the corresponding response. If not, it returns the “default” response. Note that this “default” response could not otherwise be obtained by normal means (for example, typing “default” as input) since the input is always being truncated to a maximum of four characters.
As you can see, it takes very little to add a really simple dialogue engine to your game. This might not have all the features that the Ultima games had, but serves as an illustration on how to get started.
The source code for this article is in the UltimaStyleDialogue folder at the Gigi Labs BitBucket repository.