Right, so my last dev diary was mostly theoeretical — how can one ensure every prefab and pre-placed object in Unity has a unique identifier that can then be associated with saved data and used to reassemble the game world later.
Having implemented said indexing mechanisms, there are a few gotchas to report, but it is overall working as advertised. Which means I have begun phase two: actually writing the data out.
Keep Indexing the Data
First off, my system of indexing objects pre-placed in a scene didn’t go exactly to plan. It worked perfectly for some objects but not others — while fresh objects took their identifiers just fine, I noticed that prefabs seemed to hold onto them for a bit and then lose them on entering play mode.
Turns out that if you’re fiddling with instance component values in script, you can’t just change the value and let it go (I even tried Editor.SetDirty() and this had no effect). You have to do something like this to guarantee that the values “take”:
SerializedObject serialisedObject = new SerializedObject(myComponent); serialisedObject.FindProperty(nameof(myComponent.PreplacedObjectId)).stringValue = "BLAH-1337"; serialisedObject.ApplyModifiedProperties();
(This code snippet comes from patterns to do with writing custom inspectors and editor windows, which I suppose this is more similar to than runtime game code.) Top tip — the nameof command gets compiled to the string of a property or function name from the symbol, so if you rename the function across the codebase you don’t have to worry about forgetting those magic string values in the bridges to the more shady side of Unity’s API.
The next boo-boo of mine came from applying identifiers to the prefabs themselves. The fact that I missed was the clue in the name of AssetPostprocessor — it happens after the asset has been imported, meaning that when you make and save your changes, it has to be imported again. What does that sound like to you? Why yes, that’s a recipe for infinite loops.
I caused enough editor-ending loops while testing it that I feared this method wouldn’t work at all, but it turned out that I was simply doing it wrong. The critical component is that I only set a prefab identifier if one is not already present, which ensures that there are only two import steps — the first of the object being saved initially by normal means, and the second of the identifier being worked in by the automatic script. After that, the postprocessor script sees there is a value and does not touch the prefab any further.
Actually Finally Start Saving the Data
At last, we reach phase 2: writing all that stuff out. I’m trying to keep to the simplest possible solution, which would appear to be using Unity’s JsonUtility to write serialisable classes to text files (which, yes, means players will be able to butcher their save files, but it’s totally singleplayer so go nuts. I might add a basic checksum so the game can warn you that a save has been tampered with, and therefore absolve myself from responsibility for any errors that occur after that).
The save data itself is a set of super-simple serialisable classes that are completely devoid of external references — they only contain other serialisable classes and primitive values, strings and integers and so on. Anything that was a proper reference in the runtime world is boiled down into its identifier; depending on the sort of object being saved, that might be the index of a preplaced scene object (e.g. the patrol point target of a bot) or it might be the template from which an object was created (e.g. the prefab of a health pack pick-up). I’ve split the data up into several different files for each category of data, mainly because each level you ever visit will need its own self-contained set of data (so returning to an area will maintain its changes just the same as saving and loading directly inside it).
As for what I’m saving… Although the game is complex in motion there seems to be comparatively little data that actually needs to be saved. Most of the thorny linkages are already covered by the initial spawning of an object and don’t really change after that. Most objects just need world-level properties like their positions and rotations, with the occasional lifecycle value like current health.
Having said that, I haven’t started on writing out data for my most complex structures — the units themselves. Each individual is composed of a web of components, not to mention the player’s custom paint-job and their current equipment. Oh dear…