2026-04-02 09:13:18 +02:00
2026-03-27 15:14:08 +01:00
2026-03-27 15:14:08 +01:00
2026-03-27 15:14:08 +01:00
2026-03-27 15:14:08 +01:00
2026-03-27 15:14:08 +01:00
2026-03-27 15:14:08 +01:00
2026-03-27 15:14:08 +01:00
2026-03-27 15:14:08 +01:00
2026-03-27 15:14:08 +01:00
2026-03-27 15:14:08 +01:00
2026-03-27 15:14:08 +01:00
2026-03-27 15:14:08 +01:00

Jovian Save System

A generic, game-agnostic save system for Unity supporting multiple save slots, sessions, auto-saves, and dual JSON/Binary serialization.

Requirements

  • Unity 2022.3+
  • Newtonsoft Json (com.unity.nuget.newtonsoft-json 3.2.1)

Installation

Add as a git submodule or reference via Unity Package Manager:

https://github.com/sbularca/unity-save-system.git

Architecture

ISaveSystem (facade)
├── ISaveSlotManager  — session and slot allocation
├── ISaveSerializer   — data encoding (JSON or Binary)
└── ISaveStorage      — byte-level file I/O

All components are interface-driven and injected via constructor, making them easy to swap or mock for testing.

Configuration

Settings are accessible in the Unity Editor under Project Settings > Jovian > Save System.

Setting Default Description
Save Format Json Serialization format (Json or Binary)
Max Auto Saves 3 Auto-save slots before rotation begins
Save Version 1 Version number embedded in each save for migration
Save Directory "saves" Subfolder under Application.persistentDataPath
Obfuscation Key "default-key" XOR key used by the Binary serializer

Save Slot Types

Type Behavior
Manual Player-initiated. Each save gets a new slot (manual_001, manual_002, ...)
Auto System-initiated. Creates slots up to the configured max, then rotates the oldest
Quick Single slot per session. Always overwrites the previous quick save

Quick Start

1. Define your save data

public class GameState {
    public string playerName;
    public int level;
    public float health;
}

2. Wire up the save system

SaveSystemSettings settings = SaveSystemSettings.Load();

ISaveStorage storage = new FileSystemSaveStorage(
    Application.persistentDataPath, settings.saveDirectoryName);

ISaveSerializer serializer = settings.saveFormat == SaveFormat.Binary
    ? new BinarySaveSerializer(settings.obfuscationKey)
    : new JsonSaveSerializer();

ISaveSlotManager slotManager = new SaveSlotManager(storage, settings);

ISaveSystem saveSystem = new SaveSystem(serializer, storage, slotManager, settings);

3. Create a session and save

string sessionId = saveSystem.CreateSession();

GameState state = new GameState {
    playerName = "Hero",
    level = 10,
    health = 95.5f
};

// Manual save
saveSystem.Save(sessionId, state, SaveSlotType.Manual);

// Auto save
saveSystem.Save(sessionId, state, SaveSlotType.Auto);

// Quick save
saveSystem.Save(sessionId, state, SaveSlotType.Quick);

4. Load a save

IReadOnlyList<SaveSlotInfo> slots = saveSystem.GetSlots(sessionId);

foreach(SaveSlotInfo slot in slots) {
    Debug.Log($"{slot.DisplayLabel} — {slot.TimestampDateTime}");
}

GameState loaded = saveSystem.Load<GameState>(slots[0]);

5. Async save/load

await saveSystem.SaveAsync(sessionId, state, SaveSlotType.Auto);

GameState loaded = await saveSystem.LoadAsync<GameState>(slots[0]);

Session Management

Sessions group related saves together (e.g. one playthrough).

// List all sessions
IReadOnlyList<SaveSessionInfo> sessions = saveSystem.GetAllSessions();

foreach(SaveSessionInfo session in sessions) {
    Debug.Log($"Session {session.sessionId} — Last save: {session.LastSaveDateTime}");
}

// Check if any saves exist
bool hasSaves = saveSystem.HasAnySaves();

// Delete a single slot
saveSystem.DeleteSlot(slots[0]);

// Delete an entire session and all its saves
saveSystem.DeleteSession(sessionId);

Serialization Formats

JSON (JsonSaveSerializer)

Human-readable, indented JSON. Ideal for development and debugging. Save files are plain text and easy to inspect.

Binary (BinarySaveSerializer)

Compact binary format that applies XOR obfuscation with a configurable key followed by Deflate compression. Recommended for release builds — smaller file size and not trivially editable.

Testing

The interfaces make it easy to test with an in-memory storage implementation:

public class InMemorySaveStorage : ISaveStorage {
    private readonly Dictionary<string, byte[]> files
        = new Dictionary<string, byte[]>();

    public void Write(string path, byte[] data) { files[path] = data; }
    public byte[] Read(string path) { return files[path]; }
    public bool Exists(string path) { return files.ContainsKey(path); }
    public void Delete(string path) { files.Remove(path); }
    public string[] List(string dir) {
        return files.Keys.Where(k => k.StartsWith(dir)).ToArray();
    }
    public void CreateDirectory(string path) { }

    // Async versions delegate to sync
    public Task WriteAsync(string path, byte[] data) { Write(path, data); return Task.CompletedTask; }
    public Task<byte[]> ReadAsync(string path) => Task.FromResult(Read(path));
    public Task<bool> ExistsAsync(string path) => Task.FromResult(Exists(path));
    public Task DeleteAsync(string path) { Delete(path); return Task.CompletedTask; }
    public Task<string[]> ListAsync(string dir) => Task.FromResult(List(dir));
}

Then wire it up in your test:

InMemorySaveStorage storage = new InMemorySaveStorage();
SaveSystemSettings settings = new SaveSystemSettings();
JsonSaveSerializer serializer = new JsonSaveSerializer();
SaveSlotManager slotManager = new SaveSlotManager(storage, settings);
ISaveSystem saveSystem = new SaveSystem(serializer, storage, slotManager, settings);

string sessionId = saveSystem.CreateSession();
saveSystem.Save(sessionId, myData, SaveSlotType.Manual);

IReadOnlyList<SaveSlotInfo> slots = saveSystem.GetSlots(sessionId);
MyData loaded = saveSystem.Load<MyData>(slots[0]);
Assert.AreEqual(myData.playerName, loaded.playerName);

License

Internal package — Jovian Industries.

Description
A generic, game-agnostic save system supporting multiple save slots, sessions, auto-saves, and dual JSON/Binary serialization.
Readme MIT 54 KiB
Languages
C# 100%