Building a save system in Unity
Migrated from my previous WordPress site. This is a personal game side project — the code here is prototype-quality, written for speed and playtesting, not as a polished team codebase.
As a side project I have been working on a personal game for a while. Someone suggested a developer blog about it. On a solo project where nobody else will read the code, things get messy: missing comments, short method names, and so on. That is not how I would work in a team — but it is honest about how fast prototypes get built.
Each time I make a version of the game I build it as fast as possible to test concept, playability, and most importantly: is it fun? I often throw prototypes away because the code used to ship a quick demo is useless in production.
This year I started again with a very small scope: one room, four in-game days, as a tutorial level. The goal was reusable mechanics — everything the player needs to learn the game, and everything I need for the rest of development.
I got a working demo, but play testers kept asking how to save progress. The design was one-life, yet saving still had to exist. Unity does not ship a great save system, and I did not want to buy assets for a non-product project — so I built my own.
Save system
First, base classes that hold the data:
public class WorldData
{
public string worldName = "";
public Dictionary<string, DayData> days = new Dictionary<string, DayData>();
public Dictionary<string, string> stats = new Dictionary<string, string>();
public DateTime LastPlayed;
}
public class DayData
{
public Dictionary<string, EntryData> objects = new Dictionary<string, EntryData>();
public List<string> inventory = new List<string>();
public Dictionary<string, string> stats = new Dictionary<string, string>();
}
public class EntryData
{
public Dictionary<string, string> entries = new Dictionary<string, string>();
}
Dictionaries avoid duplicate-key headaches. A helper extension handles get-or-create:
public static TValue GetSet<TKey, TValue>(
this IDictionary<TKey, TValue> dictionary, TKey key, TValue value,
GetSetType type = GetSetType.Auto)
{
// Get, Set, Overwrite, or Auto (create if missing)
}
public static void SaveEntry(this WorldData world, string dayNumber,
string entryId, string key, object value)
{
var day = world.days.GetSet(dayNumber, new DayData());
var entry = day.objects.GetSet(entryId, new EntryData());
entry.entries.GetSet(key, value.ToString(), GetSetType.Overwrite);
}
Usage stays simple:
Save(uniqueID, "PlayerName", "Bob");
string playerName = LoadString(uniqueID, "PlayerName");
Using an interface
Objects that participate implement save/load hooks:
public interface ISaveObject
{
public void OnLoad();
public void OnSave();
}
private void OnLoad()
{
transform.localPosition = SS.instance.LoadVector3("FirstPersonMovement", "position");
transform.localRotation = SS.instance.LoadQuaternion("FirstPersonMovement", "rotation");
}
private void OnSave()
{
SS.instance.Save("FirstPersonMovement", "position", transform.localPosition);
SS.instance.Save("FirstPersonMovement", "rotation", transform.localRotation);
}
Results
Serialized JSON looks like this — good enough for the tutorial room and easy to extend:
{
"worldName": "Developer world",
"days": {
"1": {
"objects": {
"FirstPersonMovement": {
"entries": {
"position": "19.651,0.03272599,19.447",
"rotation": "0,-0.0436194,0, 0.9990482"
}
}
},
"inventory": [],
"stats": {}
}
},
"stats": {},
"LastPlayed": "2024-02-22T21:50:05.5970249+01:00"
}
For what I wanted this is perfect and easy to use. More posts and a public demo link will land here when the next milestone is ready.