When I decided that procedural generation was too much bother for not enough gain and switched over to hand-crafting scenarios, I figured that was the worst of my development headaches gone. After all, the more data that is static, the less you have to worry about while the game is actually turned on.
Then I realised that you’re still going to die a reasonable amount of times during the game. Then I realised that although the levels will be individually smallish, they’ll still be quite open.
What does death plus openness equal? Why, my dear, it means I must let you save and load your game, because fuck losing all that progress (to death or dinner).
Saving and Loading
I have to admit that the question of saving and loading data is one that’s blindsided me somewhat. I’ve been happily working on my “vertical slice” scenario, a chunky little singleplayer adventure that will be composed of a hub area, a tutorial, a proper mission and maybe a deathmatch arena. Subconsciously, I expected each component scene to be small enough that I’d just save the checkpoints and be easily able to regenerate enough game state from basic parameters that I could get away with it.
Then I was working on the dialogue system and made a little side quest and… oh. Oh.
Yes, if you finished a side quest, cleaned out an area, found a cool new weapon and then lost that progress while meandering through a reasonably-sized open platter, yes, that would be shit. It’s exactly the problem that marred the otherwise fairly delightful Urban Chaos, which is a game from the late 90s and therefore had a technological excuse. I do not have any excuses. (Except that I am a single man working alone, but since when did that matter on the scales of truth and justice? Reliable saving and loading is just the Right Thing To Do.)
I think I’ve probably mentioned it before, but I do suspect that the procedural permadeath revolution in indie games is ultimately a gambit that allows developers to avoid making genuine saving and loading systems. If your character is a mere level with a couple of attributes on the side, it’s easy to save them in the abstract; and if the level is brand new every time and lacking in any narrative depth then it’s easier to handwave the replaying of it. After all, in a good procedural game, replaying is kind of the point.
While that scratches a certain kind of itch, my heart has always belonged to the giant singlplayer clunkers where everything remains where I moved it, no matter how far I progress through the game. In hindsight, it was inevitable that I’d have to solve this problem — I just wasn’t quite ready for it to pounce at crimbo time. Supposed to be a holiday, right?
I guess the problem is that saving and loading isn’t like any other other technical challenges I’ve tackled up to this point. Most things have been solvable by just jumping in and battering at them until they look right — systems like AI, for example, are self-contained enough that you can build them up by adding features as you go.
Saving and loading, though, requires careful consideration — it must be tailor made to how I specifically have built my engine so far. This is not something one can pick off the shelf and have it “just work”. Which is annoying, because it means there are basically no tutorials floating around on the subject — most articles tell you to make Serializable classes and then wave you on your way. Well, yes, of course I need to serialise the data… but how will I stitch it all back together again?
After a few weeks of thought while replaying Baldur’s Gate, I believe I have the scope of the problem:
- I will need to disambiguate between pre-placed objects and objects created at run-time. These need to be saved and loaded in different ways: pre-placed objects need to be “updated” after the level has loaded, while run-time objects need to be brought back into existence.
- In order to recreate spawned objects, I’ll need to be able to link them back to their templates. A dynamic object needs to know where it came from, plus what few extra numbers make it unique. A projectile, for example, should have position, orientation, velocity and lifespan, while the rest of the data from its template are likely to be unchanged.
- Assuming that I can know what objects are pre-placed, these need to be indexed so that on loading the level, each object can then look up what modified properties it should have instead.
- Assuming that I can know what objects were generated mid-game, they need to be loaded from disk, potentially from levels that don’t have the faintest idea what they are (e.g. the player character and their equipment is carried through from previous levels).
That’s a fair morass of pain right there, and that’s likely not even all of it. Joy!
The other issue is that, whatever answer I come up with for these problems, it has to be one that can sustain a single man working alone in his bedroom, who is prey to mood swings and architectural blunders. While I can stomach save-game data not being compatible between different versions of the game, I cannot stomach the entire thing melting subtly for some players because I forgot to increment an identifier one day. It needs to be completely automatic.
Luckily, Unity does offer some assistance with all of its (barely-documented) editor scripting hooks.
The first problem of distinguishing pre-placed objects from run-time objects, for example, appears to be solvable by hooking into the EditorSceneManager.sceneSaving event. This fires as you hit Save but before the scene is actually written to disk, allowing you to twiddle with things. I’ve slotted a PreplacedObjectId property into relevant objects that is set by this mechanism, and nothing else ever, so I should be able to rely on this as a signifier of something having been placed by me at design time versus some object at run-time by its presence or absence. As long as I do actually save my changes, which feels like a fairly reliable hook.
Indexing prefabs is also deceptively easy. Creating a subclass of AssetPostprocessor gives you a hook that fires when any file is imported by Unity. Just like with my pre-placed objects, I can use this to detect prefabs (whose paths end in .prefab) and assign them PrefabId values — I can then use this to map objects in the actual game world to the templates that spawned them. (To ensure the id values are unique, I’m just incrementing an integer each time and keeping track of it in a spare text file. Assuming I don’t import two billion files, I should be fine.)
So now I’ve got a fairly safe indexing system, how can I use that to write game state that can then be reloaded safely?
… er… I haven’t quite thought that far ahead yet… so… umm… if you’ve got any ideas… I’m thinking Resources.Load() with a mapping from id to paths?