Day 32 of 100 Days of VR: Learning about Saving and Loading Data in Unity

Josh Unity VR Development Leave a Comment

Hello and welcome back to day 32!

Today is going to be an exciting day. We’re going to work on something that we’ve never seen before in any of the tutorials: Saving and Loading data in Unity!

Specifically, we’re going to save and load our high score from our game in Unity.

From this great Unity video on how to Save and Load data and my own research, I found 3 ways for us to save data:

  1. Using PlayerPrefs
  2. Using Data Serialization
  3. Using a Server

Now with our goals set, let’s get started!

Step 1: Saving with PlayerPrefs

In the first step, we’re going to look at some of the possible ways we can save and load data.

From this great Unity video on how to Save and Load data and my own research, I found 3 ways for us to save data:

Note: for step 1, none of the code will be used in our actual game.

In fact, we’re going to make a separate script just for this.

Step 1.1: Using PlayerPrefs

The first method is PlayerPrefs.

PlayerPrefs is a class that we call that allows us to save and load simple data (think numbers and string) across multiple game sessions.

We can think of PlayerPrefs as a dictionary/table object that takes in a key/value pairing.

For those who haven’t worked with something like this before, what this means is that PlayerPrefs is an object that we can store specific values to a string and when we want to get that value back, we can give it the string.

We’ll see how this works:

  1. In our GameManager game object, click Add Component and create a new TestSaveManager

In the code, we’re just going to store a value in and then print it out to our console.

Here it is:

using UnityEngine;

public class FakeSaveManager : MonoBehaviour
{
    void Start () {
	if (PlayerPrefs.HasKey("FakeScore"))
	{
	    int savedScore = PlayerPrefs.GetInt("FakeScore");
	    print("We have a score from a different session " + savedScore);
	}
	else
	{
	    PlayerPrefs.SetInt("FakeScore", 1337);
	    print("We don't have a score yet, so we're adding one in");
	}
    }
	
}

New Variables Used

None

Walking Through the Code

PlayerPrefs is a static object that we can just access. Here’s what we did:

  1. In Start() the first thing we did was check if we stored a value to our key string: FakeScore.

From here 2 things will happen.

First Run Through

The first time we run the code, we haven’t stored anything yet, so we’ll go to our else statement and set an Int with the value 1337 to our key FakeScore.

We also print in our console notifying us that it’s the first time we did that.

Now if we run the game for the first time, here’s what we’ll see:

Notice that it says we don’t have a score yet?

Second Run Through

Now after we ran the code for the first time and we added our key/value pair to our PlayerPrefs. If we were to run the code again, we’ll go to the other code path.

In the if statement, we’ll get the value we stored inside our key “FakeScore”, which is 1337.

We’ll print out 1337.

Play our game again and here’s what we get:

See how we have a score now? That means our score persisted from our previous game. Neat!

Step 1.2: Why use PlayerPrefs

Looking at what we’ve done, when do we want to use PlayerPrefs?

The benefit of PlayerPrefs is:

  • Fast and easy to use with no setup required to store primitive variables

The con of PlayerPrefs:

  • If the data we’re storing is important, the user can easily access the data and change it

Why is it insecure you might ask?

Well, it turns out that any data that we’re saving is saved in open space on your computer. In a Windows machine, you can find these values un-encrypted in our registry. Here it is:

Do you see the 2nd item from the top of the list FakeScore and the value is 1337.

Any computer savvy hacker who has knowledge of how Unity works can easily go in and make changes to these values to increase our score.

To summarize: Only use PlayerPrefs to store information that doesn’t really affect the game, such as player settings: brightness, audio sound, special effects, etc.

Do not use it to store things like high scores or sensitive information like your credit card information.

Step 2: Saving with Data Serialization

After looking at PlayerPrefs we have another method of storing data and that is using data serialization with the help of BinaryFormatter and FileStream class.

How this method works, is that we’re:

  1. Taking data (either an object we made serializable, a primitive value, or a JSON)
  2. Convert it to numbers
  3. Storing it somewhere in our device

We’ll discuss the details further down.

Step 2.1: Using Data Serialization

I’ve made some changes to our FakeSaveManager:

using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;

public class FakeSaveManager : MonoBehaviour
{
	void Start () {
	    /*if (PlayerPrefs.HasKey("FakeScore"))
	    {
	        int savedScore = PlayerPrefs.GetInt("FakeScore");
	        print("We have a score from a different session " + savedScore);
	    }
	    else
	    {
	        PlayerPrefs.SetInt("FakeScore", 1337);
	        print("We don't have a score yet, so we're adding one in");
	    }*/
        SaveWithSerializer();
        LoadWithSerializer();
    }

    private void SaveWithSerializer()
    {
        BinaryFormatter formatter = new BinaryFormatter();
        print("Save location: " + Application.persistentDataPath);
        FileStream fileStream = File.Create(Application.persistentDataPath + "/save.dat");
        
        formatter.Serialize(fileStream, 1337);
        fileStream.Close();
    }

    private void LoadWithSerializer()
    {
        BinaryFormatter formatter = new BinaryFormatter();
        FileStream fileStream= File.Open(Application.persistentDataPath + "/save.dat", FileMode.Open);

        print("serializer got back: " + formatter.Deserialize(fileStream));
        fileStream.Close();
    }
}

New Variables Used

None

Walking Through the Code

The first thing you might notice is that I commented out the PlayerPrefab code. We don’t need that anymore, we’re just going to focus on the serialization part.

Before we walk through the code, let’s talk about these individual classes that we’re using.

FileStream

We can think of FileStream is a class that allows us to interact with files. In this case, use File.Create() and File.Open() to give us access to these files that we want to access.

Notice how we use something called Application.persistentDataPath when we create and open a file? persistentDataPath is a file location in our computer/phone that we can use to store our game files. Each path is different depending on the OS that the game is running on.

On a windows machine, here’s where the file would be saved to:

Notice how we called our file “save.dat”, that’s just what we call it, the file can be any format we want and it’ll still function the same way.

After we set up our FileStream, we will give it to our BinarySerializer so that it can use it to store our data.

It’s also important to remember that when we’re done using a FileStream, we should always call Close() to prevent a memory leak.

BinaryFormatter

We can think of the BinaryFormatter class as a useful utility that takes in a FileStream and reads/write data into the file.

In our specific example, I put the float 1337 inside, but we can put in classes that we create ourselves.

However, if we were to create our own object, we must make sure we tell Unity to serialize it (by putting [Serialize] on top of the class name).

The two important function we have to know is Serialize() and Deserialize().

  • Serialize is how we change our data into binary and then store them in our File
  • Deserialize is how we read the data back from binary.

If we were to open the file that we saved data into, here’s what we have:

Now that we understand everything used, let’s walk through the code:

  1. In Start(), we call SaveWithSerializer() which uses a BinaryFormatter and a FileStream to help us store our data, which in this case is just a float value.
  2. We create a new FileStream with the path to where we want to save our data to. We use Application.persistentDataPath to give us a location to store our file that changes depending on what OS is running our game. This is Unity making sure we put our save files in the right location so we can access it back later no matter what platform we’re using.
  3. After we have our FileStream we would use the BinaryFormatter to Serialize our data to the path we specified. After that, make sure to close our FileStream to prevent memory leaks!
  4. After we save, we call LoadWithSerializer() we do a similar operation where we create a BinaryFormatter and a FileStream to the location of the file we previously saved.
  5. With these two objects, we Deserialize, which means we unencrypt our data and put it back into a usable form for us. At this point, we have our data back.

Step 2.2: Why Use Data Serialization?

At this point, you might guess why we might want to use Data Serialization:

  • Our data is encrypted so it’s harder for the players to find it and modify the file.
  • In our example, we only store one variable, but we can actually save a lot more than just that

However, just like the PlayerPrefab, the con is:

  • We know the location of the file, and just because it’s in binary, it doesn’t mean it can’t be reverse engineered!

When should we use Data Serialization?

The answer: almost everything! Data serialization can be used as a database for our equipment, magic spells, and more! We can also use it to store information about our player character.

My only caveat is that if the game you’re creating is multiplayer where you don’t want players to have an unfair advantage, then you DO NOT want to save data about the player locally.

We can store information that the game references where we don’t modify, such as stats of an equipment locally, however information like player inventory is best stored somewhere else.

Where is that somewhere else? Good question, that’s the topic of our next method.

Step 3: Saving Data to A Server

The final and last way for us to store data for our game are to save the data to an external web server which we control and access.

From the external server, we would keep track of the state of the player, for example:

  • What level they are
  • How much money they are
  • What items they have in their inventory

All information that is changeable by the player should be saved externally in a server, where we can do verifications to make sure the player is what they say they are.

Step 3.1: Using a Server

I’m not going to create a server, but rather discuss how we would do it if we wanted to.

The first thing we must do is host a web server online somewhere.

Once the server is up, from our game, ie the client, we would make HTTP network request to our server passing around whatever data our server requires.

A general flow of what we would expect is that:

  • Whenever we’re ready to save our data, we would send the data to our server
  • Whenever we want to load our data, we would make a request to our server for our data.

When sending data over the network, we can’t just send our whole class over, our web server have no idea what anything is.

To solve this problem, we’re going to have to convert our objects into a JSON object and then send it across the network.

I won’t go too deep into the details, but a JSON object is basically a string representation of our object that we can easily convert back and forth.

We would send this string to our server where we would parse it, validate the data, and then save it in the database.

When we’re loading information, the server would send a JSON object back to the game where we would also parse and change the JSON back into something we can use.

Step 3.2 Why use a server?

The benefit of using this approach to store our data is:

  1. Cheating is impossible. Assuming there are no vulnerabilities and we have good data checks, it’ll be very hard for the player to be able to modify their own data and have it saved on our server.
  2. Similar to Data Serialization, we can save more complex data than just primitive variables.

The cons are:

  1. We need a server, which costs money.
  2. There’s more overhead work needed. We need to create our own database, know how to set up a server, and deal with storing multiple players instead of one.

The big question here is: when should we use a server?

The answer: In any game that has multiplayer that we don’t want players to get an unfair advantage against each other. If not, there should be no problem to storing everything locally.

Conclusion

Today, we explored the options that are available to us to save data in a game.

The 3 ways we explored are:

  • PlayerPrefs
  • Data Serialization
  • Sever

Most likely, in a real game, we would use all 3, but the best way for me to summarize when to use each is:

Use PlayerPrefs to store player setting information that doesn’t affect the game itself.

Use Data Serialization to store database information that we will only reference, but not change

Use a Server when we have a multiplayer game where we want to ensure no one can cheat by sending all player data to a server where we can do validation on the authenticity of the data.

For a single player game, however, we can save the data in a serialized file or if it’s simple enough, even in our PlayerPrefs.

Now that we spent Day 32 looking at saving data, it’s finally time for us to start using it on Day 33!

I’ll see you all next time to start putting everything we learned into action!

Day 31 | 100 Days of VR | Day 33

Home

Subscribe To Our Weekly Newsletter!
Like these coding articles? Join my mailing list for the latest updates and influence the code that I write!
We hate spam. Your email address will not be sold or shared with anyone else.

Leave a Reply

Your email address will not be published. Required fields are marked *