Saving Data in Unity3D Using PlayerPrefs
Rate this tutorial
(Part 1 of the Persistence Comparison Series)
Persisting data is an important part of most games. Unity offers only a limited set of solutions, which means we have to look around for other options as well.
In this tutorial series, we will explore the options given to us by Unity and third-party libraries. Each part will take a deeper look into one of them with the final part being a comparison:
- Part 1: PlayerPrefs (this tutorial)
- Part 3: BinaryReader and BinaryWriter (coming soon)
- Part 4: SQL
- Part 5: Realm Unity SDK
- Part 6: Comparison of all these options
To make it easier to follow along, we have prepared an example repository for you. All those examples can be found within the same Unity project since they all use the same example game, so you can see the differences between those persistence approaches better.
Note that if you have worked through any of the other tutorials in this series, you can skip this section since we are using the same example for all parts of the series so that it is easier to see the differences between the approaches.
The goal of this tutorial series is to show you a quick and easy way to take some first steps in the various ways to persist data in your game.
Therefore, the example we will be using will be as simple as possible in the editor itself so that we can fully focus on the actual code we need to write.
A simple capsule in the scene will be used so that we can interact with a game object. We then register clicks on the capsule and persist the hit count.
When you open up a clean 3D template, all you need to do is choose
You can then add scripts to the capsule by activating it in the hierarchy and using
Add Componentin the inspector.
The scripts we will add to this capsule showcasing the different methods will all have the same basic structure that can be found in
The first thing we need to add is a counter for the clicks on the capsule (1). Add a
[SerializeField]here so that you can observe it while clicking on the capsule in the Unity editor.
Whenever the game starts (2), we want to read the current hit count from the persistence and initialize
hitCountaccordingly (3). This is done in the
Start()method that is called whenever a scene is loaded for each game object this script is attached to.
The second part to this is saving changes, which we want to do whenever we register a mouse click. The Unity message for this is
OnMouseDown()(4). This method gets called every time the
GameObjectthat this script is attached to is clicked (with a left mouse click). In this case, we increment the
hitCount(5) which will eventually be saved by the various options shown in this tutorials series.
PlayerPrefsExampleSimple.csin the repository for the finished version.)
Another important fact about them is that they save data in plain text, which means a player can easily change their content.
PlayerPrefsshould therefore only be used for things like graphic settings, user names, and other data that could be changed in game anyway and therefore does not need to be safe.
The usage of
PlayerPrefsis basically the same as a dictionary. They get accessed as
valuepairs where the
keyis of type
string. Each supported data type has its own function:
- SetString(key, value)
- SetFloat(key, value)
- SetInt(key, value)
PlayerPrefsexample, we create a script named
PlayerPrefsExampleSimplebased on the
In addition to the basic structure, we also need to define a key (1) that will be used to save the
PlayerPrefs. Let's call it
When the game starts, we first want to check if there was already a hit count saved. The
PlayerPrefshave a built-in function
HasKey(hitCountKey)(2) that let's us achieve exactly this. If the key exists, we read it using
GetInt(hitCountKey)(3) and save it in the counter.
The second part is saving data whenever it changes. On each click after we incremented the
hitCount, we have to call
PlayerPrefs(4) to set the new data. Note that this does not save the data to disk. This only happens during
OnApplicationQuit()implicitly. We can explicitly write the data to disk at any time to avoid losing data in case the game crashes and
OnApplicationQuit()never gets called. To write the data to disk, we call
PlayerPrefsExampleExtended.csin the repository for the finished version.)
In the second part of this tutorial, we will extend this very simple version to look at ways to save more complex data within
Instead of just detecting a mouse click, the extended script will detect
Again, to visualize this in the editor, we will add some more
[SerializeFields](1). Substitute the current one (
hitCount) with the following:
Each type of click will be shown in its own
The same has to be done for the
PlayerPrefskeys. Remove the
HitCountKeyand add three new elements (2).
There are many different ways to save more complex data. Here we will be using three different entries in
PlayerPrefsas a first step. Later, we will also look at how we can save structured data that belongs together in a different way.
One more field we need to save is the
KeyCodefor the key that was pressed:
When starting the scene, loading the data looks similar to the previous example, just extended by two more calls:
As before, we first check if the key exists in the
PlayerPrefs(4) and if so, we set the corresponding counter (5) to its value. This is fine for a simple example but here, you can already see that saving more complex data will bring
PlayerPrefsvery soon to its limits if you do not want to write a lot of boilerplate code.
The documentation tells us about one important fact though:
Note: Input flags are not reset until Update. You should make all the Input calls in the Update Loop.
The keys can be addressed via their name as string but the type safe way to do this is to use the class
KeyCode, which defines every key necessary. For our case, this would be
Those checks use
Input.GetKey()(7) and if one of the two was found, it will be saved as the
modifier(8). If neither of them was pressed (9), we just reset
default(10) which we will use as a marker for an unmodified mouse click.
The same triplet can then also be found in the click detection:
First we check if one of those two was held down while the click happened (11) and if so, increment the corresponding hit counter (12). If not (13), the
unmodfiedcounter has to be incremented (14).
Finally, we need to set each of those three counters individually (15) via
PlayerPrefs.SetInt()using the three keys we defined earlier.
Like in the previous example, we also call
Save()(16) at the end to make sure data does not get lost if the game does not end normally.
When switching back to the Unity editor, the script on the capsule should now look like this:
PlayerPrefsExampleJson.csin the repository for the finished version.)
In the previous two sections, we saw how to handle two simple examples of persisting data in
PlayerPrefs. What if they get more complex than that? What if you want to structure and group data together?
One possible approach would be to use the fact that
PlayerPrefscan hold a
stringand save a
Internally, this method uses the Unity serializer. Therefore, the object you pass in must be supported by the serializer. It must be a MonoBehaviour, ScriptableObject, or plain class/struct with the Serializable attribute applied. The types of fields that you want to be included must be supported by the serializer; unsupported fields will be ignored, as will private fields, static fields, and fields with the NonSerialized attribute applied.
In our case, since we are only saving simple data types (int) for now, that's fine. We can define a new class (1) and call it
We will keep the Unity editor outlets the same (2):
All those will eventually be saved into the same
PlayerPrefsfield, which means we only need one key (3):
As before, the
modifierwill indicate which modifier was used:
Start(), we then need to read the JSON. As before, we check if the
PlayerPrefskey exists (5) and then read the data, this time using
GetString()(as opposed to
Transforming this JSON into the actual object is then done using
JsonUtility.FromJson()(6), which takes the string as an argument. It's a generic function and we need to provide the information about which object this JSON is supposed to be representing—in this case,
If the JSON can be read and transformed successfully, we can set the hit count fields (7) to their three values.
The detection for the key that was pressed is identical to the extended example since it does not involve loading or saving any data but is just a check for the key during
In a very similar fashion,
OnMouseDown()needs to save the data whenever it's changed.
Compared to before, you see that checking the key and increasing the counter (13 - 16) is basically unchanged except for the save part that is now a bit different.
First, we need to create a new
HitCountobject (17) and assign the three counts. Using
JsonUtility.ToJson(), we can then (18) create a JSON string from this object and set it using the
Remember to also call
Save()here to make sure data cannot get lost in case the game crashes without being able to call
Run the game, and after you've clicked the capsule a couple of times with or without Shift and Control, have a look at the result. The following screenshot shows the Windows registry which is where the
The location when using our example project is
HKEY_CURRENT_USER\SOFTWARE\Unity\UnityEditor\MongoDB Inc.\UnityPersistenceExampleand as you can see, our JSON is right there, saved in plain text. This is also one of the big downsides to keep in mind when using
PlayerPrefs: Data is not safe and can easily be edited when saved in plain text. Watch out for our future tutorial on encryption, which is one option to improve the safety of your data.
In this tutorial, we have seen how to save and load data using
PlayerPrefs. They are very simple and easy to use and a great choice for some simple data points. If it gets a bit more complex, you can save data using multiple fields or wrapping them into an object which can then be serialized using
What happens if you want to persist multiple objects of the same class? Or multiple classes? Maybe with relationships between them? And what if the structure of those objects changes?
As you see,
PlayerPrefsget to their limits really fast—as easy as they are to use as limited they are.
In future tutorials, we will explore other options to persist data in Unity and how they can solve some or all of the above questions.