Saving & Loading Games

Save the player's progress to a file and load it back, using a plain C# class and JSON.

Saving is like packing a suitcase. You don't pack the whole house; you choose the things you need (health, coins, level), fold them neatly into the case (JSON text), and put it in the cupboard (a file). Loading is unpacking the suitcase and putting everything back where it belongs.

PlayerPrefs or a save file?

PlayerPrefsSave file (this page)
A few settings: volume, graphics qualityReal progress: inventory, level, position, quests
One value at a timeEverything together in one file
Hard to have several save slotsEasy: one file per slot

Step 1: describe what to save

Make a plain class that holds only the data. [System.Serializable] means "this can be turned into text".

using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class SaveData
{
    public int coins;
    public int health = 100;
    public string levelName = "Level1";
    public Vector3 playerPosition;
    public List<string> inventory = new List<string>();
}

JsonUtility saves public fields (or [SerializeField] private ones). It can't save properties, Dictionary, or references to GameObjects. Save an ID or name instead, like "sword_01".

Step 2: the save system

using System.IO;
using UnityEngine;

public static class SaveSystem
{
    private static string PathFor(int slot) =>
        Path.Combine(Application.persistentDataPath, $"save{slot}.json");

    public static void Save(SaveData data, int slot = 0)
    {
        string json = JsonUtility.ToJson(data, prettyPrint: true);
        File.WriteAllText(PathFor(slot), json);
    }

    public static SaveData Load(int slot = 0)
    {
        string path = PathFor(slot);
        if (!File.Exists(path)) return new SaveData();   // no save yet: fresh game

        try
        {
            return JsonUtility.FromJson<SaveData>(File.ReadAllText(path));
        }
        catch (System.Exception e)
        {
            Debug.LogWarning($"Save file was broken, starting fresh: {e.Message}");
            return new SaveData();
        }
    }

    public static bool HasSave(int slot = 0) => File.Exists(PathFor(slot));
    public static void Delete(int slot = 0) => File.Delete(PathFor(slot));
}
Game objectsSaveData classJSON textFile on disk
Saving goes left to right; loading goes right to left.

Step 3: use it

using UnityEngine;

public class GameSaver : MonoBehaviour
{
    [SerializeField] private Transform player;
    [SerializeField] private Wallet wallet;   // your own coin script

    public void SaveGame()
    {
        var data = new SaveData
        {
            coins = wallet.Coins,
            playerPosition = player.position,
        };
        SaveSystem.Save(data);
    }

    public void LoadGame()
    {
        SaveData data = SaveSystem.Load();
        wallet.SetCoins(data.coins);
        player.position = data.playerPosition;
    }
}

Hook SaveGame and LoadGame up to buttons with a UnityEvent, or call SaveGame at checkpoints.

What the file looks like

{
    "coins": 42,
    "health": 100,
    "levelName": "Level1",
    "playerPosition": { "x": 3.5, "y": 1.0, "z": -2.0 },
    "inventory": [ "sword_01", "potion_small" ]
}

To find it, log Application.persistentDataPath. On Windows it's inside AppData\LocalLow\<company>\<product>. It's plain text, so you can read it (and so can players).

Good habits

Common Mistakes

See also