Let's dive in!
What Exactly Is Saving And Loading In Unity And How Does It Work?
Everything in your game lives in memory while it runs, and memory is wiped the moment the game closes. Saving means writing the data you care about to disk. Loading means reading it back and putting it where it belongs.
To write data to disk, you first have to turn it into something storable, like text or bytes. That conversion is called serialization, the same word Unity uses when it saves your Inspector values. Reading it back is deserialization.
game data in memory → serialize → write to disk → read from disk → deserialize → game data again
Unity gives you three popular ways to do this, and they're built for very different jobs.
PlayerPrefs: The Quick And Tiny Option
PlayerPrefs is Unity's built-in key-value store. Probably the one you've used already in your (Udemy) tutorial games. (Yes, I'm looking at you, James Doyle!) You save a number or a string under a name, and you get it back later using that same name. It takes about three seconds to learn:
// Save
PlayerPrefs.SetFloat("musicVolume", 0.8f);
PlayerPrefs.SetInt("highScore", 1200);
PlayerPrefs.SetString("playerName", "Hero");
PlayerPrefs.Save();
// Load, with a default value if nothing was saved yet
float volume = PlayerPrefs.GetFloat("musicVolume", 1f);
int highScore = PlayerPrefs.GetInt("highScore", 0);
string playerName = PlayerPrefs.GetString("playerName", "Player");
Unity then writes PlayerPrefs to disk when the game quits, but calling PlayerPrefs.Save() after important changes protects you if the game crashes first. It's perfect for settings like volume, graphics quality, or the last level you played.
It's also limited. It only stores integers, floats, and strings, so there's no bool (use 0 and 1) and certainly no inventory. The data lives in a platform-specific spot, which on Windows is the registry, an odd home for save files. And anyone can edit it. Treat it as a notepad for settings, not a place for game progress.
PlayerPrefs Vs JSON: When Your Data Outgrows PlayerPrefs
The moment your save data has structure, like a position, a level, and a list of items, PlayerPrefs vs JSON stops being a close call. JSON wins hands down. JSON is just structured text, and Unity can turn almost any simple C# class into it with one call.
First, you describe what you want to save. The [System.Serializable] attribute is what lets Unity convert it:
[System.Serializable]
public class SaveData
{
public string playerName;
public int level;
public float health;
public Vector3 position;
public int[] inventoryItemIds;
}
Then a small helper does the saving and loading. Files go in Application.persistentDataPath, a folder Unity gives you that's safe to write to on every platform:
using System.IO;
using UnityEngine;
public static class SaveSystem
{
private static string SavePath => Path.Combine(Application.persistentDataPath, "save.json");
public static void Save(SaveData data)
{
string json = JsonUtility.ToJson(data, true);
File.WriteAllText(SavePath, json);
}
public static SaveData Load()
{
if (!File.Exists(SavePath))
{
return new SaveData(); // nothing saved yet, start fresh
}
string json = File.ReadAllText(SavePath);
return JsonUtility.FromJson<SaveData>(json);
}
}
Using it from anywhere in your game takes a few lines:
// Save
SaveData data = new SaveData();
data.playerName = "Hero";
data.level = 5;
data.health = 80f;
data.position = transform.position;
SaveSystem.Save(data);
// Load
SaveData loaded = SaveSystem.Load();
transform.position = loaded.position;
And the file it creates is plain, readable text. That's a gift when you're debugging, since you can open it and see exactly what got saved:
{
"playerName": "Hero",
"level": 5,
"health": 80.0,
"position": { "x": 1.0, "y": 0.0, "z": 3.0 },
"inventoryItemIds": [1, 4, 7]
}
JsonUtility In Unity: What It Can And Can't Do
JsonUtility in Unity is fast and built-in, but it's deliberately simple. Know its limits before you design your save data:
- Your class needs
[System.Serializable], or nothing gets saved - It saves fields, not properties
- It can't handle
Dictionary - It can't save a bare array or list at the top level, so wrap it in a class (like
SaveDataabove) - It doesn't remember derived types, so a list of base-class objects loses its subclasses
If you outgrow it, Unity offers the Newtonsoft JSON package through the Package Manager, which handles dictionaries and more complex data. For most solo projects, though, JsonUtility is plenty.
Binary: Smaller Files, Harder To Read
Binary saving writes raw bytes instead of text. Files come out smaller and slightly faster to read, but you can't open them and see what's inside. Here's the safe way to do it, using BinaryWriter and BinaryReader:
using System.IO;
using UnityEngine;
public static class BinarySaveSystem
{
private static string SavePath => Path.Combine(Application.persistentDataPath, "save.dat");
public static void Save(SaveData data)
{
using (BinaryWriter writer = new BinaryWriter(File.Open(SavePath, FileMode.Create)))
{
writer.Write(data.level); // int
writer.Write(data.health); // float
}
}
public static SaveData Load()
{
SaveData data = new SaveData();
using (BinaryReader reader = new BinaryReader(File.Open(SavePath, FileMode.Open)))
{
data.level = reader.ReadInt32();
data.health = reader.ReadSingle();
}
return data;
}
}
The catch is that you must read values in exactly the order you wrote them. Add a field later, and old save files break. And a word of warning: older tutorials still teach BinaryFormatter, but Microsoft considers it insecure and has been phasing it out. Skip it.
Binary also isn't encryption. A determined player can still read it with a hex editor. For a single-player game, that's usually fine, since people editing their own save file hurt nobody but themselves.
How BIG Games Save Their Data
You might be wondering how bigger games handle saving and loading. Take a game like Cities: Skylines 2, for example. It has thousands of buildings, roads, vehicles, and potentially hundreds of thousands of citizens. Surely the developers aren't attaching a separate save system to every single object?
Nope! The trick is to save the game state, not the actual GameObjects.
Instead of saving every object individually, the game stores the data needed to recreate the world. Think about a building: you don't need to save the entire GameObject. You only need information such as its building type, position, rotation, and any other relevant data. When the game loads, it uses that information to recreate the building from its prefab.
Large games often take this concept even further by using architectures like Entity Component System (ECS). Rather than relying on traditional GameObjects and MonoBehaviours, ECS represents objects as entities with separate data components. This makes it possible to efficiently manage and process enormous amounts of game data.
You don't need ECS for your own projects, though. The underlying principle is exactly the same whether you're making a small indie game or a massive city simulator: separate your game data from the objects used to display it.
Let's See How This Works In Practice
Imagine you're making a supermarket simulator. Your store has 100 shelves, each containing different products. Instead of saving every GameObject, you create a data structure that describes the store.
First, we create two simple classes to hold our data:
[System.Serializable]
public class StoreSaveData
{
public List<ShelfSaveData> shelves = new List<ShelfSaveData>();
public int money;
}
[System.Serializable]
public class ShelfSaveData
{
public int shelfID;
public Vector3 position;
public Quaternion rotation;
public int productID;
public int quantity;
}
Notice that we're not storing any GameObjects here. We're only storing information about them.
Next, we create a centralized SaveManager that collects this information and saves it:
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class SaveManager : MonoBehaviour
{
private string SavePath =>
Path.Combine(Application.persistentDataPath, "store.json");
public void SaveGame()
{
StoreSaveData saveData = new StoreSaveData();
// Find all shelves in the store
Shelf[] shelves = FindObjectsByType<Shelf>(
FindObjectsSortMode.None);
foreach (Shelf shelf in shelves)
{
ShelfSaveData data = new ShelfSaveData();
data.shelfID = shelf.ShelfID;
data.position = shelf.transform.position;
data.rotation = shelf.transform.rotation;
data.productID = shelf.ProductID;
data.quantity = shelf.Quantity;
saveData.shelves.Add(data);
}
// Save other game information
saveData.money = GameManager.Instance.Money;
// Convert data to JSON and write it to disk
string json = JsonUtility.ToJson(saveData, true);
File.WriteAllText(SavePath, json);
Debug.Log("Game saved!");
}
}
Now comes the interesting part: loading the game. Instead of trying to restore the original GameObjects, we simply recreate them using their prefabs and the information stored in our save file.
public void LoadGame()
{
if (!File.Exists(SavePath))
return;
// Read saved data
string json = File.ReadAllText(SavePath);
StoreSaveData saveData =
JsonUtility.FromJson<StoreSaveData>(json);
// Restore money
GameManager.Instance.Money = saveData.money;
// Recreate shelves
foreach (ShelfSaveData data in saveData.shelves)
{
GameObject shelfObject = Instantiate(
ShelfDatabase.Instance.GetShelfPrefab(data.shelfID),
data.position,
data.rotation
);
// Restore product information
Shelf shelf = shelfObject.GetComponent<Shelf>();
shelf.ProductID = data.productID;
shelf.Quantity = data.quantity;
}
Debug.Log("Game loaded!");
}
Note: This is a simplified example. A production save system would also need to handle duplicate loading, missing prefabs, object relationships, save versions, and potentially much more.
So What's Actually Happening?
The important thing to understand is that the SaveManager doesn't need to know how every individual object works internally. It simply collects the information required to recreate the store.
You could extend this system to include employees, vehicles, furniture, customer information, and practically anything else in your game.
And that's the beauty of this approach: whether you have 10 objects or 10,000, the underlying principle remains the same. You're saving the information needed to rebuild your game world, not the GameObjects themselves.
Save File Gotchas
A few habits will save you real pain:
- Add a version number. A
public int version;field in your save data lets future updates recognize and upgrade old saves. - Handle a missing file. The first launch has no save, so check
File.Existslike we did above. - Expect corrupt files. Wrap your load in a
try/catchso a bad file starts a fresh game instead of crashing it. - Save ids, not scene references. Store "item 7" or a position, never a reference to a GameObject.
- Save at safe moments. Checkpoints and menus, not every frame.
What Do I Use To Save Data In My Own Games?
So Which Approach Should You Actually Use?
It really depends, mainly on the size of your project and what you want to save:
- Settings and tiny values like volume or quality →
PlayerPrefs - Real game progress → JSON through JsonUtility in Unity, saved to
Application.persistentDataPath - Large or performance-sensitive data → binary with
BinaryWriter - Dictionaries or complex data → the Newtonsoft JSON package
- Anything an old tutorial tells you to do with
BinaryFormatter→ don't - Not sure how to save and load in Unity for your game → ask "is this a setting or progress?"
Alright, that last question should settle most of the PlayerPrefs vs JSON debate. Settings go in PlayerPrefs, progress goes in a JSON file, and you'll rarely need anything fancier.
Wrapping Up
Saving data is mostly three steps: put your data in a simple class, turn it into text or bytes, and write it to Application.persistentDataPath. Learn to save and load in Unity with PlayerPrefs for settings and JSON for progress, add a version number from day one, and your players will never have to relive that tutorial level.
~happy Coding!

No comments:
Post a Comment