Friday, May 1, 2026

PlayerPrefs vs JSON Saves in Unity

When I was building Frogo Jump, my first released Unity game, I used PlayerPrefs for things that I probably should not have used PlayerPrefs for.

At the time, it made sense to me. I was still learning Unity, and many beginner tutorials used PlayerPrefs whenever they needed to save something. So when I had a problem where I needed to remember a value, my beginner brain went: okay, PlayerPrefs saves things, so I will use PlayerPrefs.

It worked, but it was messy.

The bigger lesson I learned later is that not all game data is the same. Some data should only exist while the game is running. Some data should stay on the player's device as a setting. Some data is real save data and should be part of a proper save file.

Mixing all of that into PlayerPrefs can work for a while, but it makes the project harder to understand and harder to clean up later.

View Frogo Jump on Steam


The Frogo Jump problem

Frogo Jump has an overworld map. On that map, there are level dots. The player moves between those dots, and when the player enters one of them, the game loads the actual level scene.

The important part is what happens afterward.

If the player leaves the level and returns to the overworld, they should come back to the same level dot. Otherwise, the overworld would load again and the player could appear at the start of the map instead of the place they came from.

So the game needed to remember the last overworld position before loading the level.

As a beginner, I solved this with PlayerPrefs. I saved the position before entering the level, then loaded it again when the overworld scene came back.

Technically, this can work. But the problem is that this position was not really permanent save data. It was not a setting either. It was just temporary scene transition data.

I did not need to write that value permanently to the player's device. I only needed to carry it from one scene to another while the game was still running.


What PlayerPrefs are actually for

PlayerPrefs are useful, but the name already tells you the intended use: player preferences.

Unity describes PlayerPrefs as a class that stores player preferences between game sessions. It can store strings, floats, and integers. That makes it useful for small local values like volume settings, fullscreen mode, language choices, or other simple settings.

Settings are also usually device-specific. If I play a game on my desktop PC and set the music volume to 30%, I do not necessarily need that exact setting on another device. Maybe my laptop speakers are quieter, so I want a different volume there.

That is a good use case for PlayerPrefs. The value is small, local, and simple.

But game progress is different. Checkpoints, unlocked levels, inventories, collected items, quest states, and save slots are not just preferences. They are part of the player's progress. That kind of data should be handled more intentionally.

PlayerPrefs are also not always easy to inspect like a normal save file. Depending on the platform, Unity stores them in different places. On Windows, for example, PlayerPrefs are stored in the Windows Registry. On WebGL, Unity uses the browser's IndexedDB. That is fine for preferences, but it can become annoying when you accidentally use PlayerPrefs for the wrong kind of data and then need to reset or debug it.

Unity also notes that PlayerPrefs are stored locally without encryption, so they should not be used for sensitive data.

Source: Unity PlayerPrefs documentation.


The better solution for temporary scene data

For the Frogo Jump overworld position, I would not use PlayerPrefs today.

I would use a GameManager that stays alive between scenes. In Unity, this is commonly done with DontDestroyOnLoad. Unity's documentation explains that scene loading normally destroys current scene objects, but DontDestroyOnLoad can preserve a root object during scene loading.

That is useful for this kind of problem. The data does not need to be written permanently to the device. It only needs to stay available while the game moves from the overworld scene into a level scene and then back again.

Many projects use a singleton-style GameManager for this. The point is not that every project must use one giant manager for everything. The point is that one persistent object can hold small pieces of temporary state while scenes change.

A simple GameManager for this problem could look like this:

using UnityEngine;

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    public Vector3 LastOverworldPosition { get; private set; }
    public bool HasLastOverworldPosition { get; private set; }

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }

        Instance = this;
        DontDestroyOnLoad(gameObject);
    }

    public void SetLastOverworldPosition(Vector3 position)
    {
        LastOverworldPosition = position;
        HasLastOverworldPosition = true;
    }

    public void ClearLastOverworldPosition()
    {
        HasLastOverworldPosition = false;
    }
}

Before loading a level from the overworld, the game could store the current position like this:

GameManager.Instance.SetLastOverworldPosition(player.transform.position);

When the overworld scene loads again, the player can be placed back at that position:

if (GameManager.Instance.HasLastOverworldPosition)
{
    player.transform.position = GameManager.Instance.LastOverworldPosition;
}

This is not a full save system. It is not supposed to be one. It is just a clean way to pass temporary data between scenes.

That distinction matters a lot.

Source: Unity DontDestroyOnLoad documentation.


Temporary data, settings, and real save data

The mistake I made was not simply “using PlayerPrefs.” The mistake was treating every kind of data as if it belonged in the same place.

The overworld position in Frogo Jump was temporary data. It only mattered because the player moved from the overworld scene into a level scene and then back again. It did not need to survive forever. It did not need to be part of a save slot. It just needed to survive a scene change.

Settings are different. A music volume setting should still be there after closing and reopening the game. It makes sense for that to be stored locally on the device.

Real save data is different again. If the player unlocks World 3, finishes Level 12, collects an item, or reaches a checkpoint, that is actual progress. That should be stored in a proper save system, not scattered across random PlayerPrefs keys.

The simple mental model is this:

PlayerPrefs are for local preferences. GameManagers are useful for temporary runtime data. JSON save files are better for structured game progress.

Once I understood that separation, save systems started to make much more sense.


Why JSON is better for real save data

If I were building Frogo Jump again, I would separate real progress into a structured save file.

A JSON save file is a common way to do that in Unity because it lets you define a structured data object and convert it into a text-based format. Unity's JsonUtility can convert objects to JSON and back again, as long as the data follows Unity's serialization rules.

Instead of having many separate PlayerPrefs keys, you can create a class that represents the save file.

using System;
using System.Collections.Generic;

[Serializable]
public class SaveData
{
    public string currentWorld;
    public int highestUnlockedLevel;
    public int coins;
    public List<string> collectedItems = new List<string>();
}

This is easier to understand than spreading the same data across many keys.

PlayerPrefs.SetInt("Coins", coins);
PlayerPrefs.SetInt("HighestUnlockedLevel", highestUnlockedLevel);
PlayerPrefs.SetString("CurrentWorld", currentWorld);

The PlayerPrefs version is not automatically wrong for a tiny prototype, but it becomes messy as the game grows. With a JSON save file, the save data has a shape. You can see what belongs to the save. You can create save slots. You can delete a save file. You can back it up. You can later connect it to a cloud save system if the game needs that.

That does not mean every game needs a giant save system. A small arcade game might only need a high score. But once a game has worlds, level progress, checkpoints, items, or unlocks, a proper save structure becomes much cleaner.

Sources: Unity JSON Serialization manual, Unity JsonUtility.ToJson documentation, and Unity JsonUtility.FromJson documentation.


What should be cloud saved?

Cloud saves are another reason why it helps to separate data early.

Steam Cloud is designed to store game files on Steam's servers so players can log into Steam and access their saved games from another computer. That makes it useful for real progress data, like save slots, unlocked levels, completed worlds, collected items, or other important player progress.

But that does not mean every local setting should be treated like cloud save data. A laptop and a desktop PC can need different resolution settings, graphics settings, or audio volume. Those are local preferences, so keeping them local often makes sense.

That is why I would not think of PlayerPrefs as “bad.” I would think of them as local preference storage. The problem starts when PlayerPrefs become the place where everything goes.

Source: Steamworks Documentation: Steam Cloud.


The rule I would use now

If I were building Frogo Jump again, I would separate the data like this.

Music volume, fullscreen mode, resolution, and language would go into PlayerPrefs because they are local settings.

The overworld position before entering a level would go into a singleton GameManager because it is temporary scene transition data.

Completed levels, unlocked worlds, collected items, coins, and checkpoint progress would go into a structured save file, probably JSON for a small Unity game.

If I later added cloud saves, I would sync the real save files, not every local preference.

This is the main lesson I took from the mistake: saving data is not just about storing a value somewhere. It is about understanding how long that value should exist and what kind of data it really is.


Final thoughts

PlayerPrefs are useful. I just would not use them as my whole save system anymore.

For a beginner Unity project, it is very tempting to put everything into PlayerPrefs because it is easy and many tutorials show it early. I understand why I did it in Frogo Jump. I needed a solution, and PlayerPrefs gave me one quickly.

But after finishing a real game, I can see the problem more clearly. The better approach is to separate temporary data, local settings, and permanent progress from the beginning.

That makes the project easier to debug, easier to reset, and easier to expand later.

Related

Frogo Jump: My first finished Unity game

View Frogo Jump on Steam

View all projects

Sources and further reading

No comments:

Post a Comment

Stop Making Platformers First

A lot of indie games start as prototypes. You make something small, it feels fun, someone plays it, and suddenly the idea appears: maybe ...