Unity: Save and Load Data

Raptor Kwok
8 min readNov 30, 2020

Difficulty: ★★☆☆☆
Supported Unity version: Unity 5 or higher

It is a common task to save and load data in a game. In this short tutorial, I will cover several ways to save and load data.

Prerequisites

  • Basic knowledge of C# Unity script
  • Basic knowledge of creating simple layout in Unity

Which method should I choose?

If your data is a simple number, text or boolean (true/false), you should use Method 1.

If your data contains complex data, such as player’s location, complex object model, you should use Method 2.

Method 1: Using PlayerPrefs

This is a mechanism that remembers simple settings for the player between game sessions such as score and coins.

The scenario here is to show how to display correct message on the second screen based on the button clicked. Once either button is clicked, its represented value will be saved to PlayerPrefs, then we’ll load the PlayerPrefs in the second scene. At the second scene, it will display either “Option A is selected” or “Option B is selected”.

Step 1: Create a simple interface

To start with, create two UI buttons in the first scene, which I renamed it to FirstScene .

2 UI Buttons on screen

Step 2: Create a C# script to store the game logic

Create a C# script under Main Camera, named GameSceneController. Launch the script in Visual Studio. The script should look like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameSceneController : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{

}
// Update is called once per frame
void Update()
{

}
}

Create two functions ClickOptionA() and ClickOptionB() :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameSceneController : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{

}
// Update is called once per frame
void Update()
{

}
public void ClickOptionA()
{
PlayerPrefs.SetInt("selected_option", 1);
PlayerPrefs.Save();
SceneManager.LoadScene("SecondScene");
}
public void ClickOptionB()
{
PlayerPrefs.SetInt("selected_option", 2);
PlayerPrefs.Save();
SceneManager.LoadScene("SecondScene");

}
}

Explanation: I used PlayerPrefs.SetInt() to tell Unity that I am going to save an integer setting called “selected_option” with 1 (represents Option A) or 2 (represents Option B). Then, I called PlayerPrefs.Save(); to save the changes. After setting the value, we use SceneManager.LoadScene() to navigate to next scene, which is called SecondScene .

Apart from SetInt() , Unity also offers SetString() and SetFloat() (for values with decimal places).

Go back to Unity Editor, link the button On Click events of Button A and B to ClickOptionA() and ClickOptionB() respectively.

Step 3: Create next scene

Create another scene via Unity menu > New Scene. Create a UI Text in the scene; adjust the position and font size of the Text. Don’t forget to set the Render Mode of the Canvas to Screen Space — Camera. Save this scene with name SecondScene .

New scene with a UI Text

Next, create a C# script under the Main Camera, named SecondSceneController.

In the script, add some codes:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SecondSceneController : MonoBehaviour
{
public Text bigText;
// Start is called before the first frame update
void Start()
{

}
// Update is called once per frame
void Update()
{

}
}

We added a reference of UI Text in our script, allowing the script to control the contents of the UI Text.

Then, at the Start() function, we load the settings from PlayerPrefs and based on the value, displaying corresponding message in the UI Text.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SecondSceneController : MonoBehaviour
{
public Text bigText;
// Start is called before the first frame update
void Start()
{
int selected = PlayerPrefs.GetInt("selected_option");
if(selected == 1)
{
bigText.text = "Selected Option A";
} else if(selected == 2)
{
bigText.text = "Selected Option B";
}

}
// Update is called once per frame
void Update()
{

}
}

Once added these codes highlighted in bold, save the changes and go back to the Unity Editor. Link up the UI Text at the script:

Link up the Text

Step 4: Configure Build Settings

Go to Unity menu > File > Build Settings, drag the two scenes into the Scenes in Build section.

Make sure the 2 scenes are in the the “Scenes in Build” section

Launch FirstScene and click Play. When you select Option A, it will navigate to Second Scene and the text will become “Selected Option A”. Similar for Option B.

Completed Project of Method 1 can be downloaded here.

Method 2: Using Serialization and Deserialization

If you have some complex settings that requires the game to be remembered across scenes, you will need to use this Method, which involves two actions: Serialization and Deserialization.

Serialization is to convert an object (e.g. game object) into a series of bytes, which can be saved in either memory, file or database.

Deserialization is to convert the series of bytes back to an object.

Step 1: Create the Save File structure

We are going to create a Save File structure to define what kind of settings and properties we are going to save into the Save File.

To start with, create a C# Unity script and name it as Save , via Unity menu > Assets > Create > C# Script. For better file structure, I move it to a newly created Script folder. Launch the script and change to the following:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Save
{

}

We removed most of the codes, and added just one line: [System.Serializable] . It tells the Unity that this script is ready for Serialization.

Next, we create some simple and complex variables in the Save file:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Save
{
// Simple variable
public int score = 0;
// Complex variable
public List<Vector2> targets = new List<Vector2>();

}

We’ve added one simple variable and one complex variable, which is a list of target positions, in form of Vector2.

Step 2: Create Game Logic

In the scene, we first create an empty game object, renaming it to enemies. Reset its position to X, Y, Z = 0. Then, add a new 2D Sprite object via Unity menu > GameObject > 2D Object > Sprite; make it a child object of the enemies. In the Inspector panel of the newly created Sprite, change the Sprite to Knob (it’s a built-in image) and change the colour to red. Change the scale to X,Y,Z = 3. Rename it to enemy 1.

Our “Enemy 1”

Duplicate the sprite for two times; and change their colours for easier identification.

Next, we create another script in the Main Camera in the scene, named GameController. We appended the following functions in the script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameController : MonoBehaviour
{
private int scoreObtained = 0;
public GameObject enemies;
// Start is called before the first frame update
void Start()
{

}
// Update is called once per frame
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
RandomPosition();
}

}
private void RandomPosition()
{
for (int i = 0; i < enemies.transform.childCount; i++)
{
enemies.transform.GetChild(i).transform.position = new Vector3(Random.Range(-2, 14), Random.Range(-3, 5), 0);
}
scoreObtained = Random.Range(100, 10000);
}

}

Explanation: We created a simple game. There are three dots in the scene. When SPACEBAR is pressed, the position of the dots will change randomly. Also, the score will be updated with a random number between 100 to 10000.

The simple logic is made in the RandomPosition() function and the keyboard input function is in theUpdate() function.

After you linked up the enemies game object with the script’s enemies object, you can try to play the game.

Drag the enemies game object from Hierarchy panel

Step 3: Serializing and Deserializing Vector3

In Unity, Vector3 is not serializable. We have to add a serialization surrogate class to make it serializable. Create a new C# script named Vector3SerializationSurrogate, and replace the contents:

using System.Runtime.Serialization;
using UnityEngine;
sealed class Vector3SerializationSurrogate : ISerializationSurrogate
{
// Method called to serialize a Vector3 object
public void GetObjectData(System.Object obj,
SerializationInfo info, StreamingContext context)
{
Vector3 v3 = (Vector3)obj;
info.AddValue("x", v3.x);
info.AddValue("y", v3.y);
info.AddValue("z", v3.z);
Debug.Log(v3);
}
// Method called to deserialize a Vector3 object
public System.Object SetObjectData(System.Object obj,
SerializationInfo info, StreamingContext context,
ISurrogateSelector selector)
{
Vector3 v3 = (Vector3)obj;
v3.x = (float)info.GetValue("x", typeof(float));
v3.y = (float)info.GetValue("y", typeof(float));
v3.z = (float)info.GetValue("z", typeof(float));
obj = v3;
return obj;
}
}

Step 4: Create Save File function

Next, we create a function to generate a Save file in the GameController:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameController : MonoBehaviour
{
private int scoreObtained = 0;
public GameObject enemies;
// Start is called before the first frame update
void Start()
{

}
// Update is called once per frame
void Update()
{

}
private Save CreateSaveFile()
{
Save save = new Save();
// Handling simple variable "score"
save.score = scoreObtained;
// Handling complex variable
// Obtain the position of each child in the "enemies" game object.
for(int i = 0; i < enemies.transform.childCount; i++)
{
save.targets.Add(gameObject.transform.GetChild(i).transform.position);
}
return save;
}

}

We have added a function called CreateSaveFile() to copy the data, including the score and position of the three “enemies” to a Save game object.

Then, we create a SaveFile() function to actually save the data into the file system.

At line 4, add two required namespaces:

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

And here is the SaveGame() logic:

public void SaveGame()
{
Save save = CreateSaveFile();
SurrogateSelector surrogateSelector = new SurrogateSelector();
surrogateSelector.AddSurrogate(typeof(Vector3), new StreamingContext(StreamingContextStates.All), new Vector3SerializationSurrogate());
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.SurrogateSelector = surrogateSelector;
FileStream file = File.Create(Application.persistentDataPath + "/game.save");
binaryFormatter.Serialize(file, save);
file.Close();
Debug.Log("Game saved");
}

Explanation: we use a formatter called BinaryFormatter to serialize the data from object to binary data and save the binary data to a file called game.save.

Step 5: Create Load File function

Then, we create a LoadGame() function.

public void LoadGame()
{
if(File.Exists(Application.persistentDataPath + "/game.save"))
{
SurrogateSelector surrogateSelector = new SurrogateSelector();
surrogateSelector.AddSurrogate(typeof(Vector3), new StreamingContext(StreamingContextStates.All), new Vector3SerializationSurrogate());
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.SurrogateSelector = surrogateSelector;
FileStream file = File.Open(Application.persistentDataPath + "/game.save", FileMode.Open);
Save save = (Save)binaryFormatter.Deserialize(file);
file.Close();
for(int i = 0; i < save.targets.Count; i++)
{
enemies.transform.GetChild(i).transform.position = save.targets[i];
}
scoreObtained = save.score; Debug.Log("Game loaded");
} else {
Debug.Log("No save file is found");
}
}

Explanation: The LoadGame() function loads the game.save file from file system and reflects the changes (score and enemy positions) in game.

Step 6: Add the Save and Load buttons

We update the Update()function and assign S button for Save action, and L button for Load action:

void Update()
{
if(Input.GetKeyDown(KeyCode.Space)) {
RandomPosition();
} else if(Input.GetKeyDown(KeyCode.S)) {
SaveGame();
} else if (Input.GetKeyDown(KeyCode.L)) {
LoadGame();
}
}

Try out the Game

Back to Unity editor and play the game. Press SPACEBAR to change the position of the “enemies”, and click S to save the positions and score. Press the SPACEBAR one more time to randomize the position.

To load the saved positions, press L. You should see the position is restored.

That’s all steps for this simple demo. I hope you enjoyed it.

If you encounter any questions, please leave a comment below. I’ll be happy to help.

Completed Project of Method 2 can be downloaded here.

--

--

Raptor Kwok

I write stuffs: novels, programs, mobile apps, journal papers, book chapters, etc.