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.

← Back to home

Login