Note: while writing this article, I completely changed the way I’m editing and managing NPC dialogue. Writing this article actually led me to a new and better solution, so rather than not post the article at all, I though it would be a good lesson in using the right tool for the job.
Dialogue in RPGs can be a complex thing. There are many types of interaction with NPCs, such as plain statements, questions, conditional dialogue that depends on the game state (such as whether or not you’ve beaten a particular boss, or found a key item) and more. Designing the Dialogue system itself is a fun challenge, but an even bigger one is managing and editing the actual dialogue content. RPGs have a lot of maps and a lot of NPCs, which means managing the dialogue is important.
Love2D is a framework rather than a game engine, and as such doesn’t come with any sort of built-in dialogue editor. Tiled is my tool of choice for building maps, and it has some fantastic Object features, which I use for placing NPCs on the map. However, the custom properties for Objects are not flexible enough for the complex dialogue types I have in mind. Or at least I thought so at the time. I soon realized this meant I needed to build a custom dialogue editing tool.
But first, let’s look at the requirements.
The Types of Dialogue
As a mentioned earlier, not all RPG dialogue is as simple as an NPC saying a particular line of dialogue. Sometimes this dialogue is conditional, such that the NPC says one thing at one stage of the game, but something else after certain conditions are met. Other dialogue may be a character that cycles through a list of different statements. Yet another would be a complex cutscene that involves giving items or gold, taking items, characters joining the party and many more. Here are the dialogue types I came up with:
- Simple Statements. This is the easy dialogue where the NPC always says the same line of dialogue and nothing more.
- Cycling Statements. This is where the NPC cycles through a list of statements, such that each time you talk to them they say something different, but then start repeating the same cycle.
- Random Statements. The NPC will say one of a few lines of dialogue at random.
- Cutscenes. Complex dialogue involving things like playing sounds, giving and taking items or gold, modifying the game state, opening quests, etc.
- Questions. This is a type of conditional dialogue where the player is asked a question and the NPC will state one of two things depending on the answer.
- Conditional Dialogue. This is the most complex type, where various game conditions can determine the NPC’s dialogue, which may be any of the above types.
Some of these are rather complex and involve many parameters, such as the Conditional dialogue that must check the game state or Cutscenes where many different actions can occur. This is why I concluded that Tiled’s Object properties were not flexible enough to allow me to edit the dialogue in the map editor. For example, how do I provide the list of dialogue lines for Cycling Statements, when Tiled’s custom properties do not have a list type? How do I provide a list of Cutscene Events? Or conditions to check? My Object properties would be a total mess. I needed something better.
Building a Custom Tool and File Format
Ballad of Thuriana does have dialogue right now. In fact, it has a few of these types that actually work. But the problem is they are saved as Lua tables that have to be manually edited. This works fine when you only have a few maps, but will become ridiculously slow and tedious as the game grows, not to mention error prone. It also requires me remembering NPC ids in Tiled so I can edit their dialogue in the Lua file.
My idea to solve this was fairly simple:
- Write a simple tool that creates a single dialogue file for every map, inserting default dialogue for every NPC on the map.
- Write an editor tool that can edit this dialogue file.
The first part turned out to be very easy. Tiled can export to Lua files, which is exactly what I use for the game. Lua is also very good at editing Lua, So I wrote a simple program to open the Lua map file, loop through all its objects and create a big table of NPCs and their corresponding dialogue entries, then save it in a separate file. Tiled even has a feature allowing you to call external commands from within Tiled and add a keybinding to it. That made this process even easier. Creating my Dialogue files for each map was now a breeze.
This also meant that loading the dialogue would be a single line of code in my map loader. Nice! And since it was now a persistent part of the map, I could actually track things at runtime like an index number for the Cycling Statements. Even nicer!
Building the Editor Itself
So far I have only built one custom tool for managing game content: an Item/Icon Manager that allows me to create and edit game items (like weapons, potions and powerups) and assign them a particular icon. I can also assign names to icons that I want to use in the game that are not associated with Items, such as menu icons and even spells.
I built this tool in Love2D, not only because creating Lua data was my goal, but it also made it easy to whip up a quick Graphical Interface for the tool. I decided the same approach would work well for the Dialogue Editor.
The first reason I thought this was ideal, was because I could very easily render the map and all the NPCs in their starting locations in the map. Then I could simply click on one, which would bring up a window allowing me to edit their dialogue. I could then use a radio button to choose which dialogue type it would be, and display the proper form for each one. For example, in the simple Statement form there would be only a textbox. For Random Statements, it would have 5 or 6 textboxes. For Conditional dialogue I could have a form field for the condition it needs to check and the value it is expecting, and so on.
But how would I do Cutscenes? Those are rather complex with lots of different potential actions and lines of dialogue.
I solved this by developing a DSL (Domain Specific Language) for expressing my Cutscenes. It’s basically a CSV (Comma Separated Value) format that looks something like this:
Say:Hello, I'm Bob the Wizard, but I lost my blue book of spells.
GiveItem:healingHerb
Say:I hope you can use this
TakeItem:blueBook
Say:Thank you for finding the blue book. Now that I can read my spells, I will join your party
JoinParty
SetState:helpedBob
In this little cutscene, Bob the Wizard makes a few simple statements, but he also gives an item, takes an item, join’s the party and modifies the game state by setting helpedBob to True. Each event is on its own line, and each line begins with the name of a Cutscene event followed by any needed parameters. I can easily parse these strings and create the list of Storyboard events needed to create the Cutscene.
What this means is that I only need a single TextArea element for the Cutscene dialogue type. However, this also led me to realize that I could treat every single dialogue type the same way! Simple statements were a no brainer. Random and Cycling Statements could just be a list of Say: statements. Questions and Conditionals could have similar lists of statements that include the conditions to check, and the two possible outcomes.
Well that’s excellent. That means I don’t actually need different forms for the different dialogue types. Each one can just be a single text field.
So I set about building my Dialogue Editor. It looks kind of like this:
I even grabbed the code from the Dialogue File Generation tool and incorporated it into the Dialogue Editor so that it would automatically create the Dialogue file if it didn’t exist. So I was able to replace my old script with this tool, and even edit the Command List in Tiled.
After two days, my Dialogue Editor was nearly complete. That’s when I posted about it on Twitter and someone asked me to write this very article. And when I started writing this article, I came to a startling realization.
I no longer need the Dialogue Editor
I can do it all in Tiled. That’s right, Tiled is the gift that keeps on giving.
Remember when I said I couldn’t use Tiled because of all the different types of Dialogue options for the various types?
And now remember when I said how I created my own language for expressing Dialogue in a single text box?
Tiled custom properties have single string text boxes. As soon as I developed that little language, that made Tiled a viable option again.
Now I’m back to managing more content from Tiled, which is always a good thing. The fewer steps needed the better. And all I have to do at this point is write the parser and a few lines of code to the map loader and I’m set.
There’s a small lesson to be learned here: never be afraid to abandon one solution when a much better one presents itself. Yes, I spent over 2 days of effort building Dialogue generating and editing tools. But as soon as I figured out how to use Tiled, it rendered that work obsolete.