Unity Editor showing Save Data script creation menu with C# Script option

How to Save Game Data in Unity: A Practical Guide for Mobile Games

Small Game Mechanics & Prototyping

You just built your idle clicker. Players tap, earn coins, upgrade — and then close the app. When they come back, everything is gone. Game over. Not from losing, but from a missing save system.

Saving data is one of those things that feels optional until it isn’t. Every mobile game needs it, and Unity gives you several ways to do it. This guide walks through three approaches — from the simplest to the most robust — so you can pick the right one for your project.

Option 1: PlayerPrefs (Quick and Simple)

PlayerPrefs is Unity’s built-in key-value store. It’s the fastest way to persist small amounts of data: high scores, settings, whether the player has completed the tutorial.

// Save
PlayerPrefs.SetInt("HighScore", 4200);
PlayerPrefs.SetFloat("MusicVolume", 0.8f);
PlayerPrefs.SetString("PlayerName", "Alex");
PlayerPrefs.Save(); // Write to disk immediately

// Load
int highScore = PlayerPrefs.GetInt("HighScore", 0);       // 0 = default
float volume  = PlayerPrefs.GetFloat("MusicVolume", 1f);
string name   = PlayerPrefs.GetString("PlayerName", "Player");

Call PlayerPrefs.Save() explicitly after writing — especially on mobile, where apps can be force-quit at any time.

When to use it: Settings, single values like high scores, simple flags (tutorial completed, ads removed). It’s not designed for complex or nested data.

When to avoid it: Saving entire game states, inventories, or anything with multiple related fields. Stuffing complex data into PlayerPrefs with keys like "item_0_name", "item_0_count", "item_1_name"… quickly becomes unmanageable.

Option 2: JSON + File System (The Right Tool for Most Games)

For anything beyond simple key-value pairs, serialize your data to JSON and write it to the device’s persistent data folder. This approach scales cleanly as your game grows.

Step 1: Define your data class

Create a plain C# class to hold your game state. No MonoBehaviour, no Unity dependencies — just data.

[System.Serializable]
public class GameData
{
    public int coins;
    public int level;
    public float totalPlayTime;
    public List<string> unlockedCharacters = new List<string>();
}

Step 2: Build a SaveManager

using System.IO;
using UnityEngine;

public class SaveManager : MonoBehaviour
{
    private static string SavePath =>
        Path.Combine(Application.persistentDataPath, "save.json");

    public static void Save(GameData data)
    {
        string json = JsonUtility.ToJson(data, prettyPrint: true);
        File.WriteAllText(SavePath, json);
        Debug.Log($"Game saved to: {SavePath}");
    }

    public static GameData Load()
    {
        if (!File.Exists(SavePath))
        {
            Debug.Log("No save file found. Starting fresh.");
            return new GameData(); // Default state
        }

        string json = File.ReadAllText(SavePath);
        return JsonUtility.FromJson<GameData>(json);
    }

    public static void DeleteSave()
    {
        if (File.Exists(SavePath))
            File.Delete(SavePath);
    }
}

Step 3: Use it in your game

public class GameManager : MonoBehaviour
{
    public GameData currentData;

    void Start()
    {
        currentData = SaveManager.Load();
    }

    void OnApplicationPause(bool paused)
    {
        if (paused) SaveManager.Save(currentData);
    }

    void OnApplicationQuit()
    {
        SaveManager.Save(currentData);
    }
}

OnApplicationPause is critical on mobile — it fires when the player switches apps or the screen locks, which is the most common way mobile games are “closed.”

Where does the file go?

Application.persistentDataPath resolves to the correct platform-specific location automatically:

PlatformPath
Android/data/data/com.company.game/files/
iOS/var/mobile/Containers/Data/Application/.../Documents/
Windows (Editor)C:/Users/[user]/AppData/LocalLow/[company]/[game]/

The folder is sandboxed per app and persists across updates. You don’t need to create it — Unity guarantees it exists.

Option 3: Encrypted Save (When It Actually Matters)

JSON files on Android are readable by anyone with USB debugging enabled. For casual games with cosmetic-only saves, that’s fine. But if your save file contains anything that affects what the player paid for — like “ads removed” or unlocked characters — you should encrypt it.

Here’s a simple XOR encryption layer you can drop in:

using System;
using System.Text;

public static class SaveEncryption
{
    private const string Key = "your-secret-key-here"; // Change this

    public static string Encrypt(string data)
    {
        var sb = new StringBuilder();
        for (int i = 0; i < data.Length; i++)
            sb.Append((char)(data[i] ^ Key[i % Key.Length]));
        return Convert.ToBase64String(Encoding.UTF8.GetBytes(sb.ToString()));
    }

    public static string Decrypt(string data)
    {
        string decoded = Encoding.UTF8.GetString(Convert.FromBase64String(data));
        var sb = new StringBuilder();
        for (int i = 0; i < decoded.Length; i++)
            sb.Append((char)(decoded[i] ^ Key[i % Key.Length]));
        return sb.ToString();
    }
}

Then update SaveManager to use it:

public static void Save(GameData data)
{
    string json = JsonUtility.ToJson(data);
    string encrypted = SaveEncryption.Encrypt(json);
    File.WriteAllText(SavePath, encrypted);
}

public static GameData Load()
{
    if (!File.Exists(SavePath)) return new GameData();

    string encrypted = File.ReadAllText(SavePath);
    string json = SaveEncryption.Decrypt(encrypted);
    return JsonUtility.FromJson<GameData>(json);
}

XOR is not military-grade security — a determined person can break it. But it stops casual file editing and makes your save data unreadable at a glance, which is enough for most mobile games.

Handling Save File Corruption

Files can get corrupted — mid-write power loss, storage errors, or a bug in your serialization. A corrupted save that crashes the game on launch is the worst experience you can give a returning player.

Wrap your Load() in a try-catch and fall back to a fresh state:

public static GameData Load()
{
    if (!File.Exists(SavePath)) return new GameData();

    try
    {
        string json = File.ReadAllText(SavePath);
        return JsonUtility.FromJson<GameData>(json);
    }
    catch (Exception e)
    {
        Debug.LogWarning($"Save file corrupted: {e.Message}. Resetting.");
        DeleteSave();
        return new GameData();
    }
}

For extra safety on important saves, maintain a backup file: write to save_backup.json after every successful save, and fall back to it if the primary file fails to load.

Quick Decision Guide

Your situationBest approach
High score, volume, settingsPlayerPrefs
Game state with multiple fieldsJSON + File
Unlocks tied to IAPJSON + Encryption
Multiplayer or cloud syncBackend (Unity Gaming Services, PlayFab)

Common Mistakes to Avoid

Not saving on pause. On mobile, players switch apps constantly. If you only save on OnApplicationQuit, you’ll lose data every time. Always implement OnApplicationPause.

Saving every frame. File I/O is slow. Save on pause, on level completion, and on significant events — not in Update().

Forgetting default values. When you add a new field to GameData later, existing save files won’t have it. JsonUtility.FromJson will silently set it to zero/null. Always assign sensible defaults in your constructor or field initializers.

Using PlayerPrefs for everything. It’s fine for settings. It’s a maintenance nightmare for game state. Once you have more than five or six related values, switch to JSON.

Putting It Together

For most Unity mobile games, the JSON approach hits the right balance: it’s readable during development, handles complex data cleanly, works across platforms without any extra packages, and takes about 30 lines of code to implement.

Start with the unencrypted version while building your game. Add encryption in the week before launch if your save file contains anything worth protecting.

A solid save system is invisible to players when it works — and catastrophic when it doesn’t. Get it right early and you’ll never have to think about it again.

Building a game with persistent progression? Check out our guides on creating an idle clicker in Unity and building a tower defense prototype — both are great projects to wire up a save system to.

Leave a Reply

Your email address will not be published. Required fields are marked *