# Pixeldex · Unity JSON Save / Load Starter

Drop-in versioned save / load system for Unity 2022.3+. Persists a `SaveData` class as JSON in PlayerPrefs, with schema versioning baked in so adding new fields next year doesn't corrupt existing players' saves.

License: **CC0**.
Source: https://pixeldex.dev/prompts/unity-json-playerprefs-save

---

## What's in this folder

| File | Where | What it does |
|---|---|---|
| `SaveSystem.cs` | `Assets/Scripts/` | Contains the `SaveData` class + static `SaveSystem` helper (Save/Load/Has/Delete). |

That's the whole thing. One file. No Editor utility needed.

---

## Usage

```csharp
// Save the current state
SaveData data = new SaveData {
 level = 3,
 playtime = 1234.5f,
 lastScene = "Forest",
 score = 9000,
 playerName = "Zara"
};
SaveSystem.Save(data);

// Load
SaveData loaded = SaveSystem.Load();
Debug.Log($"Level: {loaded.level}, score: {loaded.score}");

// Check if a save exists
if (SaveSystem.Has()) { /* show "continue" button */ }

// Wipe the save
SaveSystem.Delete();
```

---

## How versioning works

`SaveSystem.SCHEMA_VERSION` is a `const int` at the top of the file. Right now it's `1`.

When you change `SaveData` in a way that's NOT backwards-compatible (renaming a field, changing a type, removing a field):

1. **Bump `SCHEMA_VERSION`** to the next number (`2`, `3`, etc).
2. **Add a case to `Migrate()`** describing how to read the old shape and produce the new one. Example for a v1→v2 upgrade where you split `playerName` into `firstName` + `lastName`:

```csharp
private static SaveData Migrate(SaveData old)
{
 if (old.version == 1)
 {
 // v1 had `playerName` as one field. v2 splits it.
 string[] parts = (old.playerName ?? "").Split(' ', 2);
 old.firstName = parts.Length > 0 ? parts[0] : "";
 old.lastName = parts.Length > 1 ? parts[1] : "";
 old.playerName = null;
 }
 old.version = SCHEMA_VERSION;
 return old;
}
```

3. The next time players load, their saves auto-upgrade. Nobody loses progress.

For **adding** a new field with a sensible default, you don't even need a Migrate case, JsonUtility will leave the new field at its default (`0`, `null`, `""`, etc) when loading old JSON.

---

## What the prompt asked for that's baked in

- ✓ No `try/catch` swallowing all exceptions, catches the right exception only
- ✓ Logs a warning (not a throw) on bad data / migration
- ✓ No async / coroutines, saves are synchronous and rare
- ✓ Single file, includes both `SaveData` and the static helper

---

## Pairing ideas

- Pair with the [top-down player controller starter](https://pixeldex.dev/prompts/unity-top-down-2d-player-controller) to save player position on scene unload.
- Pair with the [coyote-time platformer starter](https://pixeldex.dev/prompts/unity-2d-platformer-coyote-time) to persist high scores or level progress.
- For multiplayer / cloud saves: this same pattern works with any string-storage backend. Swap `PlayerPrefs.SetString` / `GetString` for an HTTP call to your backend; the migration logic stays identical.

---

If you ship something with this, a link to https://pixeldex.dev in your README helps. Not required.
