What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
For most Unity games, use PlayerPrefs for small preferences and a versioned data file under Application.persistentDataPath for player progress. Serialize a plain data model—not scene objects—then validate it when loading and keep a backup so one interrupted write does not erase the only save. Add cloud storage when players need cross-device recovery; use server-authoritative data when the values must be trusted.
Choose the right kind of persistence
“Save data” can mean three different things. Keeping them separate makes the design simpler:
- Preferences: volume, language, quality, or accessibility choices. Use
PlayerPrefs. - Local game progress: level, inventory, quests, player state, and world changes. Use a file-based save system.
- Account-linked or trusted state: progress that must follow a player between devices, or values that affect competitive play or entitlements. Use cloud storage and, where necessary, server-side authority.
A local file is available offline and easy to debug, but it is tied to the device and can be edited by its owner. Cloud storage can sync devices, but it adds authentication, network failure, and conflict-resolution work. Cloud storage by itself does not make client-submitted values trustworthy.
Use PlayerPrefs for settings, not a full save system
PlayerPrefs stores integers, floats, and strings. It is a good fit for small values that are convenient but not precious:
#1 Best Overall
PlayerPrefs.SetFloat("musicVolume", 0.8f);
PlayerPrefs.Save();
float volume = PlayerPrefs.GetFloat("musicVolume", 1f);
It is not encrypted, and Unity describes it as a preferences mechanism rather than a full game-state or multiple-save-file system. Do not put credentials, payment details, secrets, or anti-cheat-sensitive values there. A large JSON save crammed into one preference string is still a poor substitute for a real save-file design. See Unity’s PlayerPrefs documentation and its persistent-data guidance. Unity documents a 1 MB WebGL PlayerPrefs limit, so test browser storage behavior for your target build.
Model the data you need to restore
A save file should describe game state using ordinary serializable data. Keep it separate from the runtime objects that use it:
- Runtime objects are scene objects and components, such as a player controller.
- Save data is the compact representation of state, such as health, inventory IDs, and a scene identifier.
- Save service handles paths, serialization, validation, backups, and errors.
Do not try to persist a GameObject, MonoBehaviour, scene reference, coroutine, socket, delegate, or transient cache. Store stable identifiers and values, then reconstruct the runtime state when loading. For example:
using System;
using System.Collections.Generic;
[Serializable]
public class SaveData
{
public int saveVersion = 1;
public string sceneName;
public float playerX;
public float playerY;
public float playerZ;
public int health = 100;
public int coins;
public List<string> inventory = new List<string>();
public List<string> completedQuests = new List<string>();
}
Use stable item and quest IDs rather than display names that might change during localization or a redesign. A world object can have a saved record such as objectId plus flags like isCollected or isDestroyed. Avoid using hierarchy positions, instance IDs, or mutable object names as permanent identifiers.
Rank #2
Write a local JSON save
JsonUtility turns supported data into JSON; it does not select a writable location or write the file. Use Application.persistentDataPath for player data, and combine paths with Path.Combine rather than building them with hard-coded separators:
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
[Serializable]
public class SaveData
{
public int saveVersion = 1;
public string sceneName;
public float playerX;
public float playerY;
public float playerZ;
public int health = 100;
public int coins;
public List<string> inventory = new List<string>();
}
public static class SaveSystem
{
private const string FileName = "save.json";
private static string SavePath =>
Path.Combine(Application.persistentDataPath, FileName);
public static void Save(SaveData data)
{
if (data == null)
throw new ArgumentNullException(nameof(data));
Directory.CreateDirectory(Application.persistentDataPath);
string json = JsonUtility.ToJson(data, true);
File.WriteAllText(SavePath, json);
}
public static bool TryLoad(out SaveData data)
{
data = null;
try
{
if (!File.Exists(SavePath))
return false;
string json = File.ReadAllText(SavePath);
data = JsonUtility.FromJson<SaveData>(json);
return data != null;
}
catch (Exception exception)
{
Debug.LogWarning($"Could not load save file '{SavePath}': {exception}");
data = null;
return false;
}
}
}
Log Application.persistentDataPath during development to find the file on the current target. Do not use Application.dataPath or Application.streamingAssetsPath for writable player saves: they are for application content or packaged assets and are not a portable writable save location.
Unity documents platform-specific persistent-data locations. Examples include Windows under the user’s AppDataLocalLow area, Android under the app’s files area, iOS in the app’s Documents directory, and WebGL in an IndexedDB-backed virtual filesystem. The path is unsupported on tvOS and returns an empty string there. Check the Unity 6.2 persistentDataPath documentation for the target platform. Unity notes that later app versions can access the same location when the bundle identifier remains the same. That is not a promise that data survives every uninstall, operating-system cleanup, user deletion, device replacement, or platform migration.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Load the data and apply it at the right time
Deserializing a file is only part of loading. Apply its values after the destination scene and its objects exist, and before enabling gameplay that could overwrite the loaded state. A basic capture-and-restore method might look like this:
using UnityEngine;
using UnityEngine.SceneManagement;
public class PlayerProgress : MonoBehaviour
{
[SerializeField] private Transform playerTransform;
[SerializeField] private int health = 100;
[SerializeField] private int coins;
public void SaveGame()
{
Vector3 position = playerTransform.position;
var data = new SaveData
{
sceneName = SceneManager.GetActiveScene().name,
playerX = position.x,
playerY = position.y,
playerZ = position.z,
health = health,
coins = coins
};
SaveSystem.Save(data);
}
public void ApplyLoadedData(SaveData data)
{
playerTransform.position = new Vector3(
data.playerX, data.playerY, data.playerZ);
health = data.health;
coins = data.coins;
}
}
For a saved game that can resume in another scene, use this sequence: read and validate the save; load the saved scene; wait for its objects and systems to initialize; restore the player and other state; then enable input. A spawn-point script or scene initialization routine can otherwise overwrite the restored transform. Scene names are convenient in a small project, but if names may change, save stable scene IDs and maintain a migration mapping.
Harden the save before relying on it
The minimal example writes directly to the main file. If the application stops during the write, the previous save may be lost or truncated. For progress the player cares about, write to a temporary file, keep a backup, validate reads, and report failures instead of implying that saving succeeded.
- Serialize the data.
- Write it to a temporary file and close it.
- Keep the current save as a backup.
- Promote the temporary file to the main path.
- Remove the temporary file after promotion succeeds.
Here is a compact version of that pattern with backup recovery and basic validation. A production implementation should also make save success visible to the caller or UI:
Free tools Windows power users keep installed
One-click scans. No signup required.
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public static class RobustSaveSystem
{
private static string SavePath =>
Path.Combine(Application.persistentDataPath, "save.json");
private static string BackupPath => SavePath + ".backup";
private static string TempPath => SavePath + ".tmp";
public static bool Save(SaveData data)
{
if (data == null)
throw new ArgumentNullException(nameof(data));
try
{
Directory.CreateDirectory(Application.persistentDataPath);
File.WriteAllText(TempPath, JsonUtility.ToJson(data, true));
if (File.Exists(SavePath))
File.Copy(SavePath, BackupPath, true);
File.Copy(TempPath, SavePath, true);
File.Delete(TempPath);
return true;
}
catch (Exception exception)
{
Debug.LogError($"Save failed: {exception}");
return false;
}
}
public static bool TryLoad(out SaveData data)
{
if (TryRead(SavePath, out data))
return true;
if (TryRead(BackupPath, out data))
return true;
data = new SaveData
{
saveVersion = 1,
sceneName = "MainMenu",
health = 100,
coins = 0
};
return false;
}
private static bool TryRead(string path, out SaveData data)
{
data = null;
try
{
if (!File.Exists(path))
return false;
data = JsonUtility.FromJson<SaveData>(File.ReadAllText(path));
if (data == null || data.saveVersion <= 0)
return false;
if (data.health < 0)
data.health = 0;
if (data.inventory == null)
data.inventory = new List<string>();
Migrate(data);
return true;
}
catch (Exception exception)
{
Debug.LogWarning($"Could not load '{path}': {exception}");
data = null;
return false;
}
}
private static void Migrate(SaveData data)
{
// Apply version-by-version changes here before using the data.
}
}
This illustrates a recovery policy, not a guarantee of identical atomic-file behavior on every target filesystem. Test interrupted writes on your target devices. If both primary and backup fail, create defaults, explain the recovery to the player, and preserve the damaged files for diagnostics when practical. Also handle low storage and other write errors. Do not swallow an exception and tell the player the save worked.
Rank #4
For valuable saves, consider rotating backups, a checksum to detect accidental corruption, timestamps, and serialized save requests so two writes cannot race. A checksum can detect damage, not prove that a player did not edit the file. Encryption can deter casual inspection, but it is not a security boundary on a device controlled by the player.
Respect JsonUtility’s serialization limits
JsonUtility is useful for straightforward Unity-style data, not a general-purpose serializer for every .NET object graph. Mark ordinary data classes with [Serializable]; public fields are the simplest reliable pattern. Dictionaries, interfaces, polymorphic structures, and many complex types need a different representation or extra handling. Convert runtime data to plain data-transfer objects, use serialization callbacks where appropriate, or choose a tested third-party serializer if the data model needs richer support. Unity’s serialization guidance describes these constraints. Avoid .NET BinaryFormatter; Unity warns that it has dangerous security vulnerabilities.
Use save slots for independent playthroughs
Give each slot its own file rather than mixing several complete saves into preferences:
string path = Path.Combine(
Application.persistentDataPath,
$"save_slot_{slotNumber}.json");
A menu can read a small summary for each slot—slot number, scene, last-save time, playtime, or display name—without loading the full world state. Keep a reserved autosave slot if useful. Confirm destructive deletion, make overwrite behavior explicit, and test creating, loading, overwriting, and deleting a slot after restarting the application. Do not overwrite the only valid copy before a new save has been written successfully.
Best Value
Version the format and migrate old saves
A save schema has a lifecycle separate from the Unity project version. Include a saveVersion field from the start. When the data shape or meaning changes, migrate old data before applying it:
private static void Migrate(SaveData data)
{
if (data.saveVersion == 1)
{
// Convert version 1 data to version 2.
data.saveVersion = 2;
}
if (data.saveVersion == 2)
{
// Convert version 2 data to version 3.
data.saveVersion = 3;
}
}
Think through added or renamed fields, changed units, item-name-to-ID conversions, renamed scenes, and reorganized inventory or quest structures. Missing fields may deserialize to defaults, but those defaults may not be valid gameplay values. Reject or deliberately handle saves from a newer unsupported version rather than interpreting them as if they were current. Keep test saves from older releases so migrations remain covered after updates.
Choose save triggers that fit the game
Good save points include an explicit Save command, a checkpoint, a completed level or major quest, a safe menu transition, or a meaningful settings change. Save when state changes—not every frame. Avoid expensive disk work in a performance-critical moment, prevent overlapping writes, and consider asynchronous operations for large saves.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →On mobile, pause or focus-loss callbacks can be useful additional opportunities to persist recent changes. Saving on application quit is only a last-chance fallback: crashes, forced termination, browser closure, and power loss can prevent a quit callback from running. If a write is queued asynchronously, make sure your lifecycle and platform strategy gives it a realistic chance to finish.
Local saves, cloud saves, and trusted state
| Option | Best for | Trade-off |
|---|---|---|
PlayerPrefs |
Small, non-sensitive settings | Limited data types; editable; no slots or built-in sync |
| Local JSON file | Offline progress, modest data, multiple slots | Device-specific and user-editable; needs recovery and migration logic |
| Binary serializer | Cases where a measured size or performance need justifies it | Harder to inspect; compatibility and platform behavior depend on the serializer and data |
| Cloud storage | Account-linked recovery and cross-device sync | Needs network handling, authentication, and conflict resolution |
| Server-authoritative backend | Competitive scores, entitlements, or values that must be trusted | More backend design and validation work |
Do not choose binary serialization simply because it is assumed to be faster; measure the actual serializer, data, and target device. Local JSON and PlayerPrefs are both editable by the player. For leaderboards, premium currency, multiplayer inventories, and other sensitive progression, the server should own the value or validate actions instead of accepting arbitrary client-written totals.
Adding Unity Cloud Save
Unity Cloud Save stores player-associated key/value data and supports access classes including default, public, and protected. Unity’s current player-data documentation lists limits of 2,000 key/value pairs and 5 MiB per access class per player; check the documentation again when implementing because service limits can change. Protected data is intended for writes from server-authoritative contexts such as Cloud Code or a game server. A client-writable cloud value is still client-controlled.
A practical offline-first flow is:
- Load and validate local data so the game can start without a network connection.
- Authenticate the player and fetch the cloud record when available.
- Compare revisions, timestamps, or write-lock information; do not blindly upload an older local save over a newer cloud copy.
- Resolve conflicts according to game rules. If neither copy is clearly correct, ask the player rather than silently discarding progress.
- Apply the chosen valid state, save it locally, and upload only when its revision is known.
Cloud storage does not automatically supply offline behavior, conflict policy, migration, or anti-cheat. Design those parts deliberately.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsQuick Recap
Troubleshooting common save failures
- It works in the Editor but not a build: log
Application.persistentDataPath, check write permissions and platform storage behavior, and inspect the actual build’s logs. - The path is empty: Unity documents tvOS as unsupported for this property. Choose a platform-appropriate storage strategy.
- The save disappears after reinstall: persistent storage is not a cloud backup. Reinstall and cleanup behavior varies; use account-linked cloud data if recovery across device removal matters.
- Values load as defaults or are missing: check the serialized field names and supported types, validate defaults, and run the correct migration before applying gameplay state.
- Inventory or dictionary data is absent: transform it into supported lists or use a serializer that supports the structure, then test on every target platform.
- The save is malformed or incomplete: attempt the backup, report recovery, and preserve the damaged file where practical.
- Loaded position changes immediately: inspect spawn and initialization scripts; apply loaded state after those systems initialize and before enabling input.
- Cloud progress goes backward: compare revisions or timestamps and define a conflict flow before upload.
Quick decision
- Saving volume or language? Use
PlayerPrefs. - Saving an offline single-player run? Use a versioned file under
Application.persistentDataPath, with validation and backup recovery. - Supporting several playthroughs? Use separate slot files and explicit overwrite/deletion flows.
- Supporting multiple devices? Add authenticated cloud storage and a conflict policy.
- Protecting competitive or monetized state? Keep authority on a server; do not trust a client save, encrypted or otherwise.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

