RPGs need to have memories. Characters need to remember that they’ve been spoken to. Quests need to remember that their objectives have been ticked off. Incidental asides need to remember that they’ve already occurred. Enemies need to remember that they’ve been killed.
A lot of these cases are handled automatically by the appropriate subsystem. For example, Exon has a whole, discrete quest system that handles objectives, their completion states and how they show up in the Datavault.
But sometimes that’s not quite right. Sometimes, you just need a dangling Boolean flag that can be checked now and again.
You shouldn’t laugh. You already know I’ve built my own trigger system for level logic, and if you’ve played Fragment then you’ve seen proof that it actually works. You may or may not be surprised to know that more than half of its conditionals are based on the world; for example, most characters will know to give you a different piece of dialogue based on whether or not an objective has been marked as completed, or if you’re carrying a particular item.
The other half are a little more hairy.
Way, way back in the mists of time when I first considered how to store variables in Exon, I started with Scriptable Objects. This meant that any level and any place in the Unity world could have a reference to a particular variable through this asset, and they’d all be confident that they’d be reading and writing to the same place.
This scared me when I discovered that although Unity acts as if Scriptable Object instances are unique, there’s actually no guarantee of this: as soon as Asset Bundles get involved, it is quite possible to have the “same” Scriptable Object loaded twice (or more), and then it’s pot luck who’s referring to which one. As such, it’s not actually safe to store live, runtime data in a Scriptable Object, which reduces those objects that are cluttering up your Assets folder to nothing more than indices into a proper runtime handler. And a whole Scriptable Object felt a little bit heavyweight for storing a link to a single teeny little value.
So I sat on this problem for… well, years. After all, I have been quite able to muddle through by checking world state. Eventually, though, I had to face it.
My first lighter-weight attempt was to add “flags” to my dialogue engine. A conversation had flags associated with it, that were only accessible within that conversation, and a single flag could be set just by entering a particular dialogue node. This made it very easy to do basic conditions like “you have spoken to this person before” and “this person now dislikes you” without cluttering up the rest of the universe.
Flags were moderately successful, but they had one irritating downside. Since they were just strings, if I wanted to rename a flag it duffed up all the references to it. Since they were handled by presence or absence, they could only encode Boolean state. Since they were in the conversation engine, they couldn’t be used in generic scenario logic. Flags were slightly too light-weight to be the perfect solution.
So I had to think to myself: how can I store and record variables without the overheads (and dangers) of Scriptable Objects, but without the ephemerality of simple key/value pairs. After all, I still want to be able to pick variables from a list, so I can never accidentally invent mismatched values.
One thing the conversation flag system did make me realise was how to get Custom Property Drawers and other custom editor components to riff off objects living wild in the scene (i.e. GameObject.Find… performance in the editor isn’t much of a concern, and the UI redraws infrequently enough that you can be rude like this and feel nothing). Flags were defined in the parent Dialogue object and any “flag” field was just a string property that used a Custom Property Drawer to turn it into a dropdown menu reading from the predefined list. I liked this.
I want a shiny and reliable authoring experience that maps to something very simple and lightweight underneath. So when I built the Variable Manager component, I made it automatically assign an ID, independent from its name, when a new variable is added to the list, which means there will never be a clash — I can give two variables the same name or rename them and the underlying link through the ID will remain. The dropdown pickers can use the friendly names too. (This is pretty much a retread of how the save/load system maps arbitrary IDs to real objects.)
I also want to limit variable scope. Most variables only apply to a single character, quest or level, and would be at best confusing clutter or at worst dead weight for the rest of the campaign. Luckily with a single component handling variables this is easy — I have one attached to the Level controller, and one attached to the Campaign object, and it’s a single extra dropdown to choose which scope to use. The level-specific variable manager has a huge list of people whom you’ve spoken to, while the campaign variable manager can just remember who you pissed off enough to have them come back for revenge in Chapter 3.
The final step was deciding how to tie this back into the conversation system. In the end, as is the way, I jemmied the UI: I made the Dialogue Node editor able to render (a simplified version of) the trigger editor. Once I attach a trigger to a node, its actions show up for editing in-situ. I also put a button in to create a trigger, so that I can quickly create those little variable-setting stubs without having to jump over to the full trigger editor… but it’s the same logic, so any changes to the trigger editor window will seamlessly carry over.
Huzzah for custom editor tooling!