Skip to main content

๐Ÿ’พ Lesson 5.3: Saving with PlayerPrefs

Everything you've built so far vanishes the moment the game quits. PlayerPrefs is Unity's simplest way to make a few values stick between sessions โ€” the master volume, the last selected level, a high score. It's a tiny key-value store, dead easy to use, and easy to misuse. This lesson shows exactly what it's for, what it is emphatically not for, and where those saved values actually live.

๐ŸŽฏ Learning Objectives

By the end of this lesson, you will be able to:

  • Store and read values with SetInt/SetFloat/SetString and their Get counterparts
  • Understand when writes actually hit disk and why you call PlayerPrefs.Save()
  • Provide sensible defaults and check existence with HasKey
  • Delete individual keys or wipe everything with DeleteKey / DeleteAll
  • Wire a settings menu to PlayerPrefs so choices persist
  • Explain what PlayerPrefs is good for (settings, high score) and why it's wrong for complex or secure data
  • Know where PlayerPrefs is stored on Windows, macOS, and other platforms

Estimated Time: 35 minutes  ยท  Prerequisite: Lesson 5.2 (Additive & Multi-Scene Workflows)

In This Lesson

A Tiny Key-Value Store

PlayerPrefs stores named values that survive between play sessions. You write with a key (a string like "MasterVolume") and a value; later you read it back by the same key. It supports exactly three types โ€” int, float, and string โ€” and nothing else.

Think of it as a small dictionary Unity saves to disk for you. No file paths, no serialization, no setup. That simplicity is its whole appeal and also the source of every mistake people make with it.

๐Ÿ“– Definition

PlayerPrefs: a static Unity class that persists small key-value pairs (int, float, string) across sessions. Values are stored per user, per application, in a platform-specific location (the Windows registry, a macOS .plist, and so on). It is meant for preferences and other tiny bits of state โ€” not for a full save game.

The API: Set, Get, Save

The entire everyday API fits on one screen. Writing:

using UnityEngine;

// Store values (key first, value second)
PlayerPrefs.SetFloat("MasterVolume", 0.75f);
PlayerPrefs.SetInt("LastLevel", 3);
PlayerPrefs.SetString("PlayerName", "Ray");

// Flush the writes to disk right now.
PlayerPrefs.Save();

Reading back โ€” later, or after a restart:

float volume = PlayerPrefs.GetFloat("MasterVolume", 1f); // 1f = default if missing
int   level  = PlayerPrefs.GetInt("LastLevel", 0);
string name  = PlayerPrefs.GetString("PlayerName", "Player");

The Get methods take an optional second argument: the default returned when the key doesn't exist yet (a brand-new player has no saved volume). Always supply it โ€” it saves you a pile of "why is my volume 0 on first launch?" bugs.

โš ๏ธ Set doesn't always mean saved

The Set calls update an in-memory copy. Unity automatically writes that to disk when the application quits normally (via OnApplicationQuit). But if the game crashes, or you stop Play mode abruptly, un-flushed changes can be lost. Call PlayerPrefs.Save() after important changes to force the write immediately. Don't call it every frame โ€” it's a disk operation.

Rounding out the API, you can check for and remove keys:

if (PlayerPrefs.HasKey("PlayerName")) { /* it exists */ }

PlayerPrefs.DeleteKey("LastLevel");  // remove one key
PlayerPrefs.DeleteAll();             // wipe EVERYTHING for this app

โš ๏ธ DeleteAll is a sledgehammer

PlayerPrefs.DeleteAll() erases every key your game has ever stored โ€” settings, high scores, the lot. It's handy for a "Reset to defaults" button during development, but never wire it to something a player can hit by accident. Prefer DeleteKey for surgical removals.

Defaults & HasKey

Because a fresh install has no saved data, robust code always plans for "the key isn't there yet." You have two clean patterns:

  1. Default argument (preferred for simple values): PlayerPrefs.GetInt("LastLevel", 0) returns 0 when the key is missing.
  2. HasKey guard (when the very first run needs special handling): check PlayerPrefs.HasKey(...), and if it's the first launch, seed defaults and maybe show a welcome flow.
void ApplyOrSeedSettings()
{
    if (!PlayerPrefs.HasKey("MasterVolume"))
    {
        // First ever launch โ€” seed sensible defaults.
        PlayerPrefs.SetFloat("MasterVolume", 0.8f);
        PlayerPrefs.SetInt("Fullscreen", 1);
        PlayerPrefs.Save();
    }

    AudioListener.volume = PlayerPrefs.GetFloat("MasterVolume");
    Screen.fullScreen    = PlayerPrefs.GetInt("Fullscreen") == 1;
}

Notice PlayerPrefs has no bool type, so booleans are stored as an int: 1 for true, 0 for false. That's a standard idiom you'll see constantly.

A Settings Menu Bound to PlayerPrefs

The textbook use of PlayerPrefs is an options screen. A volume slider, a fullscreen toggle, and a graphics dropdown โ€” each reads its saved value on open and writes it back on change. Here's the panel we're wiring, drawn as it appears in-game:

A game Settings menu bound to PlayerPrefs A recreation of an in-game Settings panel. It has a title "Settings", a Master Volume slider at about 75 percent labelled with the PlayerPrefs key MasterVolume (float), a Fullscreen toggle switched on labelled Fullscreen (int 0/1), a Quality dropdown reading "High" labelled QualityLevel (int), and Apply and Reset buttons at the bottom. โš™๏ธ Settings Master Volume 75% โ†ณ PlayerPrefs key: "MasterVolume" (float) Fullscreen On PlayerPrefs key: "Fullscreen" (int 0/1) Quality High โ–พ PlayerPrefs key: "QualityLevel" (int) Apply Reset
Figure 1: A Settings menu backed by PlayerPrefs (faithfully recreated). Each control maps to one key: a float for volume, an int 0/1 for the toggle, and an int for the quality index.

The script behind it is short. On Start it loads saved values into the controls; each control's callback writes back:

using UnityEngine;
using UnityEngine.UI;

public class SettingsMenu : MonoBehaviour
{
    [SerializeField] Slider volumeSlider;
    [SerializeField] Toggle fullscreenToggle;
    [SerializeField] Dropdown qualityDropdown;

    void Start()
    {
        // Load saved prefs into the UI (with first-launch defaults)
        volumeSlider.value      = PlayerPrefs.GetFloat("MasterVolume", 0.8f);
        fullscreenToggle.isOn   = PlayerPrefs.GetInt("Fullscreen", 1) == 1;
        qualityDropdown.value   = PlayerPrefs.GetInt("QualityLevel", 2);

        // Apply them to the actual systems
        ApplyAll();
    }

    public void OnVolumeChanged(float v)
    {
        PlayerPrefs.SetFloat("MasterVolume", v);
        AudioListener.volume = v;
    }

    public void OnFullscreenChanged(bool on)
    {
        PlayerPrefs.SetInt("Fullscreen", on ? 1 : 0);  // bool -> int
        Screen.fullScreen = on;
    }

    public void OnQualityChanged(int index)
    {
        PlayerPrefs.SetInt("QualityLevel", index);
        QualitySettings.SetQualityLevel(index);
    }

    // Hook to the Apply button to flush everything to disk.
    public void Apply()
    {
        PlayerPrefs.Save();
    }

    void ApplyAll()
    {
        AudioListener.volume = volumeSlider.value;
        Screen.fullScreen    = fullscreenToggle.isOn;
        QualitySettings.SetQualityLevel(qualityDropdown.value);
    }
}

Hook each control's On Value Changed event to the matching method in the Inspector, and the Apply button to Apply(). That's a complete, persistent options screen.

โœ… Pro Tip

Define your keys as const string in one place (const string VolumeKey = "MasterVolume";) and reference the constant everywhere. A typo in a raw string like "MastreVolume" silently reads/writes the wrong key with no compiler error โ€” the exact class of bug we warned about with Animator parameter names in Module 1.

Where Is It Stored?

PlayerPrefs doesn't create a file you choose. Unity picks a per-platform location keyed by your Company Name and Product Name (set in Project Settings โ–ธ Player):

  • Windows: the registry, under HKCU\Software\[CompanyName]\[ProductName].
  • macOS: a preferences .plist at ~/Library/Preferences/unity.[CompanyName].[ProductName].plist.
  • Linux: a file under ~/.config/unity3d/[CompanyName]/[ProductName]/.
  • Android / iOS: the OS's shared-preferences / NSUserDefaults store for the app.
  • WebGL: the browser's IndexedDB for that page origin.

Two consequences follow from this. First, because the path uses Company and Product name, renaming your product orphans the old prefs โ€” the game looks like a fresh install. Second, on desktop the values are stored in plain text / plain registry entries a curious player can open and edit.

๐Ÿ’ก Editor vs. build. In the Editor, PlayerPrefs uses a location tied to the Editor and your project, separate from a built game's store. So a value you saved while testing in Play mode won't magically appear in a standalone build, and vice versa โ€” that's expected, not a bug.

What PlayerPrefs Is NOT For

Because it's so easy, people reach for PlayerPrefs to store their entire save game โ€” serializing an inventory into one giant string, cramming level layouts into keys. Resist that. PlayerPrefs is the right tool for a narrow job and the wrong one for everything else.

flowchart TD A["What am I saving?"] --> B{"Small, flat
preference/score?"} B -- "Yes" --> C["โœ… PlayerPrefs
volume ยท last level ยท high score ยท toggles"] B -- "No" --> D{"Structured game state?
inventory ยท stats ยท position"} D -- "Yes" --> E["๐Ÿ“„ JSON save file
(next lesson)"] D -- "Sensitive / anti-cheat?" --> F["๐Ÿ”’ Server or
encrypted storage"]

Figure 2: Choosing where data lives. PlayerPrefs handles the small, flat stuff; structured state belongs in a JSON file; anything cheat-sensitive belongs on a server.

Specifically, do not use PlayerPrefs for:

  • Complex / structured data โ€” an inventory, a list of quests, a world layout. There's no nesting, no arrays, no objects. You'd be hand-serializing into strings, which is exactly what a real save system (next lesson) does properly.
  • Large data โ€” it's tuned for small values. Megabytes of game state will be slow and clumsy.
  • Secure or anti-cheat data โ€” it's trivially editable on desktop. Currency, unlocks, or leaderboard scores that matter must be validated server-side, never trusted from PlayerPrefs.

โš ๏ธ "But I could just JSON it into a string keyโ€ฆ"

You can shove a serialized blob into a single string key, and plenty of tutorials do. But you lose every advantage of a real file (readable on disk, versionable, easy to back up) and gain the registry's size limits and awkward tooling. If your data is structured, write a proper JSON save file instead โ€” which is precisely what Lesson 5.4 builds.

Hands-on Challenge

๐Ÿ‹๏ธ Exercise 1: A persistent high score

Objective: Save and display a high score that survives restarts.

  1. Write a HighScore class with a method Report(int score).
  2. Inside, read the current best with PlayerPrefs.GetInt("HighScore", 0). If the new score beats it, SetInt the new value and Save().
  3. Display the stored value in a UI Text on start.
  4. Enter Play mode, report a few scores, stop, and re-enter โ€” confirm the best persists.
๐Ÿ’ก Hint: it resets every time I press Play

Two likely causes: you're calling DeleteAll or re-seeding somewhere on Start, or you never called Save() and stopped Play mode before Unity auto-flushed. Add a Save() right after a new best is written.

โœ… Success check

The high score only ever goes up, shows the correct value immediately on launch, and remembers it after fully quitting and reopening a build.

๐Ÿ‹๏ธ Exercise 2: Wire the settings panel

Build the Settings menu from Figure 1: a volume Slider, a Fullscreen Toggle, and a Quality Dropdown. Attach the SettingsMenu script and hook each control's On Value Changed event plus the Apply button. Confirm your choices reload correctly after restarting, and add a Reset button that calls DeleteKey on just those three keys (not DeleteAll).

๐ŸŽฏ Quick Quiz

Question 1: Which of these is the right job for PlayerPrefs?

Question 2: You called PlayerPrefs.SetInt(...) but the value is lost after a crash. Why?

Question 3: How do you store a bool in PlayerPrefs?

Summary

๐ŸŽ‰ Key Takeaways

  • PlayerPrefs is a tiny key-value store for int, float, and string values that persist between sessions.
  • Set* updates memory; call PlayerPrefs.Save() after important changes so a crash can't lose them.
  • Always pass a default to Get*, and store bools as an int (1/0).
  • Use HasKey to detect a first launch; use DeleteKey for surgical removal and reserve DeleteAll for dev resets.
  • Storage is per-platform (Windows registry, macOS .plist, etc.), keyed by Company/Product name โ€” and it's editable, so it's not secure.
  • Great for settings and high scores; wrong for structured, large, or cheat-sensitive data.

๐Ÿš€ What's Next?

PlayerPrefs handles the small, flat stuff beautifully. But a real save game โ€” the player's position, their stats, an inventory of items โ€” is structured, and that's precisely what PlayerPrefs can't do. In Lesson 5.4: A JSON Save/Load System we build a proper save file with JsonUtility and write it to disk, tying the whole module together in a mini-project.

๐Ÿ’พ You can persist settings now

A few lines of Set, Get, and Save give you options that stick and scores that stay. Just remember what PlayerPrefs is not for โ€” that judgment is half the skill.