PlayerPrefs
Whether you’re relatively new to developing in Unity or you’ve got a few projects under your belt, you’ve most likely realized how useful it would be for players to save data.
If you’re like most of us, your first instinct might be to do a quick search on how to save player data in Unity. From this search, you may be surprised that there are quite a few different answers, all varying in complexity.
The simplest solution is a Unity class called PlayerPrefs
. In this article, we’ll go over what PlayerPrefs
is, how, when, and when not to use it. We’ll also discuss reasonable alternatives to using PlayerPrefs
.
PlayerPrefs
in UnityPlayerPrefs
PlayerPrefs
PlayerPrefs
PlayerPrefs
PlayerPrefs
in UnityWith PlayerPrefs
, you can have an easy-to-use interface that allows you to start persisting some data!
Let’s say you’ve made a simple game with some number of levels. You’ve decided you want to save the level your player is on when they quit the game so that the correct level loads back up when they start again.
Well, this looks pretty easy in PlayerPrefs
. All you need to do to save the level is the following:
<pre> // currentLevel is an integer variable representing the level the player is on PlayerPrefs.SetInt("Level", currentLevel); PlayerPrefs.Save(); </pre>
When you’re ready to load, getting the saved value is just as easy:
<pre> // You can set a default return value if you haven't saved a "Level" yet with the second parameter currentLevel = PlayerPrefs.GetInt("Level", 0); </pre>
This certainly seems to be about as easy as it gets, so it’s time to save some data, right?
Well, sort of. The short answer is that saving a game state like this is generally a bad idea.
The longer answer is that saving and loading data in Unity is actually a very deep topic without a clear best solution.
PlayerPrefs
Let’s start by taking a closer look at PlayerPrefs
to see if it’s something we want to use to save our game data. A good first step in this process is to check the docs for PlayerPrefs
. Right away, some things should raise some eyebrows.
First of all, PlayerPrefs
saves data on a per-application basis. This means that if you want to have more than one save file for your application, you need to get creative. For example, you could choose to append a save file ID
to each entry in PlayerPrefs
and only load and save based on that ID
.
Of course, then you also need to find a way to save a list of player ID
s, which brings us to the next issue with using PlayerPrefs
: you can only save data as a string, float, or integer.
You probably know that there are more data types than just string, float, and integer. You can probably envision a scenario where you would want to use a data type other than these three. This should be a big red flag for us when trying to figure out if this class is suitable for saving game data.
If we need to construct some sort of complicated string parsing system to mimic the behavior of a simple list, we might be barking up the wrong tree.
Another important tidbit we can extract from the docs is where the location of the data from PlayerPrefs
is actually saved. This may be something you haven’t given much thought to if you’re relatively new to programming, but saved data is just data that exists somewhere in a file.
That file might be on a different computer across the country in the case of an online application, or it might just be in a sub folder called data
. The file could be structured like a database or could be a simple text file, it just depends on what the application developers felt like doing.
In this case, if we are on a Windows computer, we can see from the docs that PlayerPrefs
entries are saved to the registry. This should be another red flag when considering saving game data with PlayerPrefs
. The registry is really not used for that sort of thing and mainly saves system and application configuration data.
With all these flags and eyebrows popping up, it’s pretty natural to wonder what exactly PlayerPrefs
is used for if not for saving player progress data. Well, the first clue (and point of confusion) comes from the name.
PlayerPrefs
If you thought that the name “PlayerPrefs” alludes to the preferences of the player, you’re certainly not alone, but you are also incorrect.
The “Player” in “PlayerPrefs” actually refers to the Unity Player, the name of the standalone Unity Engine application that gets built when you turn your game into an application of its own. You may have come across this naming convention when changing settings for your project in Unity, as the runtime options are under Player Settings.
With this in mind, we can start to find some reasonable use cases for PlayerPrefs
.
Most projects made with Unity will probably want to launch with a specific resolution or quality level. While we could choose to save settings related to these things with the rest of our game data, it is also reasonable to assume that a comfortable resolution isn’t really relevant for each save, but is more relevant to the application with respect to the computer it runs on.
In this case, saving resolution with PlayerPrefs
could make perfect sense. With each new save file your users create, the application resolution remains at whatever they initially set. This is more optimal than defaulting to a value that may not even work on each user’s machine each time they start a new save.
The PlayerPrefs
class is not an ideal way to save game or per user data for a Unity project. The data types are very restrictive and data cannot be easily separated from each user. However, it is an excellent interface for saving configuration and settings information relevant to each specific application, such as resolution, quality level, default audio device, and more.
So if we aren’t going to save our game data using PlayerPrefs
, what are we going to do? The first step is to really think about the specifics of your application and how user data is going to be handled.
For example, it’s a good idea to think about what would happen if the user could access the data saved by your application. Realistically, any local saving system (including using PlayerPrefs
) will lead to this possibility.
If the answer is that it would be catastrophically bad, then you probably already know that you need to be saving user data off-site on a separate server (as is the case with almost all online applications). If you are making an offline application, however, the answers can vary pretty wildly.
In the case of a game, having your users access their own save data is probably not the worst thing, but it could lead to manipulation of the game state or even accidental corruption of their progress. There isn’t a lot you can do about the latter, since no matter what format your data is stored in, it will likely always be possible to find a way to randomly remove a chunk of it. For the former, there are a couple of options that range from not worrying about it to encrypting your save data to make it more difficult, though not impossible, to manipulate.
PlayerPrefs
To keep things simple, let’s focus on saving data for a local application. In essence, all we need to do to save player data is write information to a file. In Unity, the simplest way to do this is with a StreamWriter
and JsonUtility
.
StreamWriter
will let us actually write data to a file, and JsonUtility
will handle converting the data we want to save into JavaScript Object Notation (JSON).
While we aren’t actually using any JavaScript, JSON has become one of the standards for transmitting data due to its convenience for web applications. It is very easily read by humans and maps very simply to JavaScript objects.
For our purposes, we will let Unity handle the actual translation to and from JSON via JsonUtility
. JsonUtility
is sort of a blessing and a curse, since it is very fast but has several limitations.
Before implementing this in your projects, I encourage you to read the docs. It’s very beneficial to understand some of the limitations of JsonUtility
before going all-in for a project.
Let’s take a look at an example before the weeds get too thick.
<pre> using System.IO; using UnityEngine; public class DataManager { private Data gameData; private static string dataFilePath = Path.Combine(Application.persistentDataPath, "GameData.json"); public DataManager(int level = 0) { gameData = new Data(); gameData.level = level; } // Here we set our level with some sort of GameManager public void SetLevel(int level) { if (gameData == null) { gameData = new Data(); } gameData.level = level; } // The method to return the loaded game data when needed public Data GetGameData() { return gameData; } public void Save() { // This creates a new StreamWriter to write to a specific file path using (StreamWriter writer = new StreamWriter(dataFilePath)) { // This will convert our Data object into a string of JSON string dataToWrite = JsonUtility.ToJson(gameData); // This is where we actually write to the file writer.Write(dataToWrite); } } public void Load() { // This creates a StreamReader, which allows us to read the data from the specified file path using (StreamReader reader = new StreamReader(dataFilePath)) { // We read in the file as a string string dataToLoad = reader.ReadToEnd(); // Here we convert the JSON formatted string into an actual Object in memory gameData = JsonUtility.FromJson<Data>(dataToLoad); } } [System.Serializable] public class Data { // The actual data we want to save goes here, for this example we'll only use an integer to represent the level public int level = 0; } } </pre>
This example should be enough to start saving and loading data in your local Unity projects. Web-based projects are complex enough that we won’t be covering data management practices for them here.
Obviously, there is a bit more overhead here than with the PlayerPrefs
class. However, once you have a simple save and load set up, it’s almost as easy to use in practice. One thing to note here is that the data saved in GameData.json
is going to be easily readable and, therefore, modifiable.
This means that in its current state, it wouldn’t be too difficult for an end user to modify their save data. To make this more difficult, we would need to look into different methods of encryption for our data.
Encrypting save data is a bit beyond the scope of this article, but there are many thorough resources available on the topic if you are so inclined to search. For now, as an alternative to PlayerPrefs
for local, nonsensitive data, the simple file operations outlined here are a great way to get started saving game data.
PlayerPrefs
So what about PlayerPrefs
? Since it’s an excellent system for saving computer-specific information, we can modify our example to also save some settings data in a way that doesn’t interfere with our file operations and will persist across users on the same computer.
<pre> using System.IO; using UnityEngine; public class DataManager { private Data gameData; private SettingsData settingsData; private static string dataFilePath = Path.Combine(Application.persistentDataPath, "GameData.json"); public DataManager(int level = 0) { gameData = new Data(); gameData.level = level; settingsData = new SettingsData(0, 1920, 1080); } // Here we set our level with some sort of GameManager public void SetLevel(int level) { if (gameData == null) { gameData = new Data(); } gameData.level = level; } // The method to return the loaded game data when needed public Data GetGameData() { return gameData; } // This can be used when we change settings values public void SetSettings(int quality, int horizontalResolution, int verticalResolution) { settingsData = new SettingsData(quality, horizontalResolution, verticalResolution); } public void SaveSettings() { PlayerPrefs.SetInt("QualityLevel", settingsData.qualityLevel); PlayerPrefs.SetInt("HorizontalResolution", settingsData.horizontalResolution); PlayerPrefs.SetInt("VerticalResolution", settingsData.verticalResolution); PlayerPrefs.Save(); } public void LoadSettings() { // We can get a default value if there is no settings information, which is what the second parameter // in the PlayerPrefs.GetInt(p1, p2) is for settingsData = new SettingsData( PlayerPrefs.GetInt("QualityLevel", 0), PlayerPrefs.GetInt("HorizontalResolution", 1920), PlayerPrefs.GetInt("VerticalResolution", 1080) ); } public void Save() { // This creates a new StreamWriter to write to a specific file path using (StreamWriter writer = new StreamWriter(dataFilePath)) { // This will convert our Data object into a string of JSON string dataToWrite = JsonUtility.ToJson(gameData); // This is where we actually write to the file writer.Write(dataToWrite); } // This doesn't need to be coupled to the Save function, and probably shouldn't be, // but for demonstration purposes we'll put this here SaveSettings(); } public void Load() { // This creates a StreamReader, which allows us to read the data from the specified file path using (StreamReader reader = new StreamReader(dataFilePath)) { // We read in the file as a string string dataToLoad = reader.ReadToEnd(); // Here we convert the JSON formatted string into an actual Object in memory gameData = JsonUtility.FromJson<Data>(dataToLoad); } LoadSettings(); } public class SettingsData { public int qualityLevel = 0; public int horizontalResolution = 1920; public int verticalResolution = 1080; public SettingsData(int quality, int hr, int vr) { this.qualityLevel = quality; this.horizontalResolution = hr; this.verticalResolution = vr; } } [System.Serializable] public class Data { // The actual data we want to save goes here, for this example we'll only use an integer to represent the level public int level = 0; } } </pre>
While the code presented here is not exactly game ready, hopefully the concepts explained through it will be useful in your Unity project development.
That’s it! I hope that the uses of PlayerPrefs
are a little more clear.
This is a topic that I found somewhat confusing when I started working in Unity. However, at the end of the day, it’s really up to you for how to handle the data in your project. Every problem comes with a wide range of potential solutions.
I hope you make something awesome!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowSOLID principles help us keep code flexible. In this article, we’ll examine all of those principles and their implementation using JavaScript.
JavaScript’s Date API has many limitations. Explore alternative libraries like Moment.js, date-fns, and the new Temporal API.
Explore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.