Process

Save Systems That Do Not Corrupt: A Practical Unity Guide

Lost progress is one of the few bugs players never forgive. Here is how to build a Unity save system that survives crashes, updates, and two devices disagreeing.

Vectra Play 6 min read
Program code filling a screen

A save system that does not corrupt needs three things: atomic writes so a crash cannot leave a half-written file, a version number so old saves survive updates, and a tested restore path. Serialization format matters far less than these three, and PlayerPrefs provides none of them.

Why this matters

Players forgive bugs. They do not forgive losing progress, and a corrupted save produces one-star reviews faster than almost anything else in a mobile game.

Corruption is also a late-appearing problem. It works in development, works in testing, and starts appearing when thousands of real sessions meet real interruptions.

The fixes are well understood and cheap to implement at the start.

Why PlayerPrefs is not a save system

PlayerPrefs is a settings store, and it works well for settings.

It falls short as a save system for four reasons.

Use it for volume, language, and whether the tutorial was seen. Keep progress out of it.

The question is not whether PlayerPrefs can hold your data. It is what happens the first time a player takes a phone call during a save.

Serialization options compared

Pick based on what you need to do with the file, not on preference.

FormatStrengthsWatch out for
JSONReadable, easy to debug, easy to versionLarger files, slower for big saves
BinaryCompact and fastHard to debug, fragile across type changes
MessagePack or similarCompact and reasonably fastExtra dependency, less readable
ScriptableObjectConvenient in-editorNot a runtime save mechanism, a common mistake

For most mobile games, JSON is the right default. Saves are small, debugging matters more than milliseconds, and being able to read a support ticket's attached save file is genuinely valuable.

If files grow large, compress the JSON rather than switching to binary. You keep the debuggability and get most of the size back.

Atomic writes, and why corruption happens

This is the single most important section, and it is a few lines of code.

Corruption happens when a write is interrupted. The file is opened, truncated, and partially written when the process is killed by a crash, a battery cutoff, or the operating system reclaiming memory. What remains is a valid file containing invalid data.

The fix is to never write over the live file.

  1. Write the new save to a temporary file.
  2. Flush it and close it, ensuring it reached storage.
  3. Rename the temporary file over the real one, which is atomic on the platforms that matter.
  4. Keep the previous file as a backup before the rename.

With this pattern, an interruption at any point leaves either the old save or the new one intact, never a hybrid.

Add a checksum to the file and verify it on load. If it fails, fall back to the backup. That combination handles nearly every corruption case players actually hit.

Versioning saves across updates

Every save should carry a version number from the first build, before you think you need one.

The read path then becomes: read the version, run any migrations needed to bring it up to current, then load.

Skipping the version number is the most common cause of an update that wipes progress, and it is the one players complain about loudest.

Cloud save and conflict resolution

Cloud save turns a storage problem into a distributed systems problem, which is worth knowing before enabling it.

The hard case is two devices with diverging progress. Something has to choose, and every automatic choice loses somebody's data.

Three workable approaches.

Whichever you choose, keep a local backup of the overwritten save. A player who lost progress to a bad merge can then be recovered by support.

If a backend is involved, the wider question of what to build and what to rent is in Owning Your Backend.

Testing a save system properly

Save systems pass casual testing and fail in the field, so test the failure paths deliberately.

  1. Kill the app mid-save. Force-close during a write and confirm the game recovers.
  2. Corrupt a file on purpose. Truncate it, edit a byte, and confirm the fallback works.
  3. Load an old save from a previous build, every release.
  4. Fill the disk and confirm a failed write does not destroy the existing save.
  5. Reinstall and confirm cloud restore behaves as designed.
  6. Change the device clock if you use timestamps anywhere.

Automate the first three. They are quick, and they catch regressions that are otherwise found by players.

What we would do

Build the atomic write and the version number on day one. Both are small, and both are painful to retrofit once saves exist in the wild.

Use JSON, compress it if it grows, and keep one backup file plus a checksum. That combination covers the overwhelming majority of real-world corruption.

Add cloud save only when the game genuinely needs it, and decide the conflict rule before you turn it on rather than after the first support ticket.

The short version

Add atomic writes and a version number to your save code this week. If progress loss is already showing up in your reviews, send us the build.

Related reading: Object Pooling in Unity, Owning Your Backend, and Does Your Game Need a Backend.

#Unity#Mobile#Development#Process
Your turn

Got a game idea? We build it.

You bring the concept. We design, build, test and launch it, and you own 100% of the finished game.

Share Your Game Idea  →