【Unity】Easy Data Saving in Unity: How to Use PlayerPrefs and Its Pitfalls

Created: 2025-12-07Last updated: 2026-07-12

PlayerPrefs is Unity's built-in feature for saving simple data like volume settings and high scores. Learn the basics of SetInt and GetInt, cases where you shouldn't use it, and why encryption matters.

Those volume settings the player carefully adjusted—gone the moment they restart the game. A game that doesn't save its data goes back to "meeting you for the first time" on every launch. The easiest way to save small pieces of data like volume, graphics quality, and high scores is Unity's built-in PlayerPrefs.

This article covers the basics of PlayerPrefs (Set/Get/Save), the pitfalls and cases where you shouldn't use it that hide behind its convenience, and how to wire it into a real options screen.

Conceptual image of PlayerPrefs: a small clay drawer cabinet where a volume knob, a high-score medal, and a graphics-quality gear are stored in labeled, keyed drawers

What You'll Learn

  • The basics of PlayerPrefs—storing data as key-value pairs
  • The Set/Get methods and the golden rule of default values
  • When to call Save() (protecting against crashes)
  • When NOT to use it—no encryption, only three data types
  • Choosing the right tool: PlayerPrefs for settings, JSON files for save data

Sponsored

Basic Usage of PlayerPrefs

PlayerPrefs supports only three data types: int, float, and string. Each type has a corresponding Set method (for saving) and Get method (for loading).

Diagram of key-value drawers: a drawer labeled MasterVolume holds 0.8, the HighScore drawer holds 1200, and the PlayerName drawer holds a name card, illustrating how data is stored and retrieved by key name

Saving Data: The Set Methods

To save data, use SetInt(), SetFloat(), or SetString(). The first argument is a key (a string) that identifies the data, and the second argument is the value you want to save.

using UnityEngine;

public class GameSettings : MonoBehaviour
{
    public void SaveVolume(float volumeLevel)
    {
        // Save the float volume value under the key "MasterVolume"
        PlayerPrefs.SetFloat("MasterVolume", volumeLevel);
    }

    public void SaveHighScore(int score)
    {
        // Save the int high score under the key "HighScore"
        PlayerPrefs.SetInt("HighScore", score);
    }
}

Important: Calling a Set method alone only changes the data in memory—it hasn't been written to disk yet. To prevent data loss if the game crashes, it's safest to call PlayerPrefs.Save() right after saving important data to explicitly flush it to disk.

public void SaveAllSettings()
{
    PlayerPrefs.SetInt("QualityLevel", 2);
    PlayerPrefs.SetFloat("MusicVolume", 0.8f);

    // Immediately write the in-memory changes to disk
    PlayerPrefs.Save();
}
Diagram of the Set/Save relationship: Set only writes to memory and can be lost on a crash, while calling Save() writes to disk so the data survives a power-off
Sponsored

Loading Data: The Get Methods

To load saved data, use GetInt(), GetFloat(), or GetString(). The first argument is the key you used when saving.

Here, specifying a default value as the second argument is extremely important. If no data exists for the given key (e.g., on the game's first launch), this default value is returned. Without a default value, a missing key returns 0 or an empty string, which can cause unintended behavior.

How default values work: if the key exists, the saved 1200 is returned; if the key is missing on first launch, the default value 0 passed as the second argument of GetInt is returned
using UnityEngine;
using UnityEngine.UI;
using TMPro;   // TextMeshPro is the current standard for text display

public class LoadSettings : MonoBehaviour
{
    public Slider volumeSlider;
    public TextMeshProUGUI highScoreText;

    void Start()
    {
        // Load the "MasterVolume" data; use the default value 1.0f if it doesn't exist.
        float savedVolume = PlayerPrefs.GetFloat("MasterVolume", 1.0f);
        volumeSlider.value = savedVolume;

        // Load the "HighScore" data; use the default value 0 if it doesn't exist.
        int highScore = PlayerPrefs.GetInt("HighScore", 0);
        highScoreText.text = "High Score: " + highScore;
    }
}

tips: For why you should use TextMeshProUGUI instead of the legacy Text for text display, see the TextMeshPro article.

Other Useful Methods

  • PlayerPrefs.HasKey(string key): Returns a bool indicating whether data exists for the given key. Handy when you want to branch logic for the first launch.
  • PlayerPrefs.DeleteKey(string key): Deletes the data for the given key.
  • PlayerPrefs.DeleteAll(): Deletes all PlayerPrefs data. Useful for a game reset feature, but handle it with care—it's extremely powerful.

Pitfalls and Limitations of PlayerPrefs

PlayerPrefs is very quick and convenient, but it's not a silver bullet. It's important to understand the cases where you shouldn't use it.

1. Security Issues

Data saved with PlayerPrefs is not encrypted. Any moderately tech-savvy user can easily find the stored file and modify its contents. Storing tamper-sensitive data like high scores or in-game currency directly in PlayerPrefs is very risky.

Diagram of PlayerPrefs tampering risk: the saved HighScore is visible in plain text, and a user can easily rewrite it to 999999. Don't store important data as-is

Countermeasures:

  • If you need to store important data, always apply your own encryption before writing it to PlayerPrefs.
  • Alternatively, skip PlayerPrefs entirely and consider a more robust save system: write the data to a file in JSON or binary format and encrypt the file itself.

2. Storing Complex Data

PlayerPrefs can only handle int, float, and string. It's a poor fit for structured data such as a player's inventory (a list of items), character stats, or quest progress.

Countermeasures:

  • You can use a library like JsonUtility or Json.NET to serialize instances of your own classes into JSON strings and save them with PlayerPrefs.SetString(). This is a relatively easy and common approach.
  • For larger, more complex save data, creating a dedicated save file (e.g., in binary format) is better in terms of performance and extensibility.

Practical: Building an Options Screen That Saves Volume

Whether it's an action game, an RPG, or a puzzle game, every released title has a "volume slider on the options screen." And the spec is always the same—apply instantly when moved, save on close, restore on the next launch. Let's actually build the most classic use of PlayerPrefs.

The volume-setting save flow: move the slider, save with SetFloat and Save() when closing, and it is restored to the same position on the next launch

Wire the following script to a uGUI Slider (see UI Basics).

using UnityEngine;
using UnityEngine.UI;

public class VolumeSettings : MonoBehaviour
{
    private const string VolumeKey = "MasterVolume";   // Make the key name a constant (prevents typo accidents)

    [SerializeField] private Slider volumeSlider;

    void Start()
    {
        // On launch: restore the saved volume (default 1.0 on first run)
        float saved = PlayerPrefs.GetFloat(VolumeKey, 1.0f);
        volumeSlider.value = saved;
        AudioListener.volume = saved;

        // When the slider moves, apply to audio instantly (don't save yet)
        volumeSlider.onValueChanged.AddListener(v => AudioListener.volume = v);
    }

    // Called from the "close" button on the options screen
    public void SaveAndClose()
    {
        PlayerPrefs.SetFloat(VolumeKey, volumeSlider.value);
        PlayerPrefs.Save();   // Flush to disk once, at close time
        gameObject.SetActive(false);
    }
}

There are two key points.

  • Apply every frame, but save only "on close": Calling Save() every time the slider moves causes frequent disk writes. The standard pattern is to split them—apply to audio (AudioListener.volume) instantly, and write to disk just once when the screen closes.
  • Restore and default value go together: If you pass a default value in GetFloat(key, 1.0f) when restoring in Start(), the game naturally starts at "max volume" even on the first launch.

If you want separate sliders for BGM and SE, just split the keys into "BGMVolume"/"SEVolume" and repeat the same shape. For full-fledged volume control with a mixer (Audio Mixer), see the audio basics article.

Bonus: Good to Know for Later

  • The bool-saving idiom: bool values can't be stored directly, so the standard trick is to convert them to int 0/1: PlayerPrefs.SetInt("IsTutorialDone", isDone ? 1 : 0) and GetInt("IsTutorialDone", 0) == 1.
  • Turn key names into constants: Writing string keys like "MasterVolume" all over your code turns typos into bugs. It's best to centralize them in one place, e.g., public static class PrefsKeys { public const string MasterVolume = "MasterVolume"; }.
  • Where the data actually lives: On Windows it's stored in the registry (HKCU\Software\[CompanyName]\[ProductName]), on macOS/iOS in plist files, and on Android in SharedPreferences. If you "can't find the file," that's because it's in the registry.
  • Editor and builds are separate: PlayerPrefs in the Editor is stored in a different location than in a built player. If "data saved in the Editor doesn't show up on the device," that's normal behavior.

Summary

PlayerPrefs is the perfect entry point to data persistence in Unity.

  • Easily stores three types—int, float, and string—as key-value pairs.
  • Save with the Set methods, load with the Get methods. After an important save, call Save().
  • With Get methods, the golden rule is to specify a default value in case the data doesn't exist.
  • The data is not encrypted, so it's unsuitable for tamper-sensitive data like high scores.
  • Storing complex data structures requires extra work, such as serializing to JSON.

Understand the strengths and weaknesses of PlayerPrefs and use the right tool for the job: PlayerPrefs for simple options like volume settings, and a more robust file-based system for complex, important information like save data. We cover how to build a full save system in the JSON save article.