EventGet 50% off your ticket to MongoDB.local NYC on May 2. Use code Web50!Learn more >>
MongoDB Developer
C#
plus
Sign in to follow topics
MongoDB Developer Centerchevron-right
Developer Topicschevron-right
Languageschevron-right
C#chevron-right

Saving Data in Unity3D Using PlayerPrefs

Dominic Frei11 min read • Published Feb 07, 2022 • Updated Sep 07, 2022
UnityRealmC#
Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
(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 2: Files
  • 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.
The repository can be found at https://github.com/realm/unity-examples, with this tutorial being on the persistence-comparison branch next to other tutorials we have prepared for you.

Example game

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 GameObject -> 3D Object -> Capsule.
You can then add scripts to the capsule by activating it in the hierarchy and using Add Component in 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 HitCountExample.cs.
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 hitCount accordingly (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 GameObject that 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.

PlayerPrefs

(See PlayerPrefsExampleSimple.cs in the repository for the finished version.)
The easiest and probably most straightforward way to save data in Unity is using the built-in PlayerPrefs. The downside, however, is the limited usability since only three data types are supported:
  • string
  • float
  • integer
Another important fact about them is that they save data in plain text, which means a player can easily change their content. PlayerPrefs should 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.
Depending on the operating system the game is running on, the PlayerPrefs get saved in different locations. They are all listed in the documentation. Windows, for example, uses the registry to save the data under HKCU\Software\ExampleCompanyName\ExampleProductName.
The usage of PlayerPrefs is basically the same as a dictionary. They get accessed as key/value pairs where the key is of type string. Each supported data type has its own function:
  • SetString(key, value)
  • GetString(key)
  • SetFloat(key, value)
  • GetFloat(key)
  • SetInt(key, value)
  • GetInt(key)
For the PlayerPrefs example, we create a script named PlayerPrefsExampleSimple based on the HitCountExample shown earlier.
In addition to the basic structure, we also need to define a key (1) that will be used to save the hitCount in the PlayerPrefs. Let's call it "HitCountKey".
When the game starts, we first want to check if there was already a hit count saved. The PlayerPrefs have 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 SetInt(key, value) on 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 Save() (5).

Extended example

(See PlayerPrefsExampleExtended.cs in 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 PlayerPrefs.
Instead of just detecting a mouse click, the extended script will detect Shift+Click and Ctrl+Click as well.
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 Inspector element.
The same has to be done for the PlayerPrefs keys. Remove the HitCountKey and add three new elements (2).
There are many different ways to save more complex data. Here we will be using three different entries in PlayerPrefs as 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 KeyCode for 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 PlayerPrefs very soon to its limits if you do not want to write a lot of boilerplate code.
Unity offers a detection for keyboard clicks and other input like a controller or the mouse via a class called Input. Using GetKey, we can check if a specific key was held down the moment we register a mouse click.
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.
Therefore, we also need to implement the Update() function (6) where we check for the key and save it in the previously defined modifier.
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 KeyCode.LeftShift and KeyCode.LeftControl.
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 modifier to the 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 unmodfied counter 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:

More complex data

(See PlayerPrefsExampleJson.cs in 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 PlayerPrefs can hold a string and save a JSON in there.
First we need to figure out how to actually transform our data into JSON. The .NET framework as well as the UnityEngine framework offer a JSON serializer and deserializer to do this job for us. Both behave very similarly, but we will use Unity's own JsonUtility, which performs better in Unity than other similar JSON solutions.
To transform data to JSON, we first need to create a container object. This has some restriction:
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 HitCount:
We will keep the Unity editor outlets the same (2):
All those will eventually be saved into the same PlayerPrefs field, which means we only need one key (3):
As before, the modifier will indicate which modifier was used:
In Start(), we then need to read the JSON. As before, we check if the PlayerPrefs key exists (5) and then read the data, this time using GetString() (as opposed to GetInt() before).
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, HitCount.
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 Update():
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 HitCount object (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 PlayerPrefs.
Remember to also call Save() here to make sure data cannot get lost in case the game crashes without being able to call OnApplicationQuit().
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 PlayerPrefs get saved.
The location when using our example project is HKEY_CURRENT_USER\SOFTWARE\Unity\UnityEditor\MongoDB Inc.\UnityPersistenceExample and 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.

Conclusion

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 JSON.
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, PlayerPrefs get 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.
Please provide feedback and ask any questions in the Realm Community Forum.

Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Related
Code Example

How to Use MongoDB Client-Side Field Level Encryption (CSFLE) with C#


Sep 23, 2022 | 18 min read
Quickstart

MongoDB & C Sharp: CRUD Operations Tutorial


Sep 23, 2022 | 12 min read
Tutorial

Designing a Strategy to Develop a Game with Unity and MongoDB


Jun 12, 2023 | 7 min read
Tutorial

Integrate Azure Key Vault with MongoDB Client-Side Field Level Encryption


May 24, 2022 | 9 min read
Table of Contents
  • Example game