1. Put the saved state in one class
Don't save GameObjects or MonoBehaviours. Save plain data: numbers, strings, vectors and lists. One class keeps it easy to version later.
using UnityEngine;
[System.Serializable]
public class SaveData
{
public int version = 1;
public int coins;
public Vector3 checkpoint;
public Color playerColor = Color.white;
}2. Write and read the file
Application.persistentDataPath points to a folder that survives updates on every platform. Write to a temporary file first and then swap it in, so a crash mid-write never corrupts the player's save.
using System.IO;
using UnityEngine;
public static class SaveSystem
{
static string FilePath => Path.Combine(Application.persistentDataPath, "save.json");
public static void Save(SaveData data)
{
string tmp = FilePath + ".tmp";
File.WriteAllText(tmp, JsonUtility.ToJson(data, true));
if (File.Exists(FilePath)) File.Replace(tmp, FilePath, null);
else File.Move(tmp, FilePath);
}
public static SaveData Load()
{
if (!File.Exists(FilePath)) return new SaveData();
return JsonUtility.FromJson<SaveData>(File.ReadAllText(FilePath));
}
public static void Delete()
{
if (File.Exists(FilePath)) File.Delete(FilePath);
}
}3. JSON file or PlayerPrefs?
| JSON file | PlayerPrefs | |
|---|---|---|
| Best for | Game progress, inventories, worlds | Settings: volume, quality, last menu tab |
| Size | Any size | Small values |
| Inspect and back up | Open the file in any editor | Hidden in the registry or a plist |
| WebGL | Needs IndexedDB sync | Works out of the box |
A common setup is both: JSON for the game, PlayerPrefs for settings. To store JSON in PlayerPrefs instead, call PlayerPrefs.SetString("save", json) and then PlayerPrefs.Save().
4. Decide when to save
- On events: picking up a coin, reaching a checkpoint, finishing a quest. Players never lose more than a moment.
- On quit:
OnApplicationQuit, andOnApplicationPause(true)on mobile, where apps get killed without warning. - Not every frame: disk writes are slow. Mark the data dirty and save once at the end of the frame or on a timer.
Keep the version field from step 1. When you add fields in an update, old saves still load: missing fields get their default values, and you can migrate based on the version number.
Skip the setup: a ready save system
The Save & Load System for Unity is this pattern finished: one controller, a JSON/PlayerPrefs checkbox, checkpoints, coins, colour platforms and a demo level, with full C# source. It pairs well with the Inventory System and the Character Customizer, which saves outfits the same way.
Questions
In Application.persistentDataPath. On Windows that is under AppData/LocalLow/<company>/<product>; each platform has its own location.
JsonUtility is built in and fast but limited (no dictionaries or properties). Newtonsoft Json.NET handles complex types; Unity ships it as the com.unity.nuget.newtonsoft-json package.
You can't fully stop it on their machine. For single-player games that's usually fine; if it matters, add a checksum or encrypt the file.
Who wrote this
Imersa Studios builds the Unity systems, low poly packs and field guides sold on itch.io. The code and techniques here come from shipping those products. More about the studio.

