Persistent Data – Saving and Loading Data

Using JSON to read and write complex objects such player Position and player Colours

You don’t have access to this lesson
Please register or sign in to access the course content.
Length: 60 minutes|Difficulty: Hard

There is no live WebGL demo for this example due to it requiring saving and loading of data[6].

Use the previous lesson to help with setting up Save and Load buttons on the scene.

We are going to create a class that will be saved as a JSON (JavaScript Object Notation) object.

JSON is a standard format for setting out data that is used in a wide range[7] of places such including the Web and some Object Orientated databases.

Unity has a built in JsonUtility that can convert some objects to json and back again.

In this example we will be saving the player colour, a player name and the player position.

You can save some simpler Unity objects such as Color and Vector3 objects. Complex[8] objects like transforms cannot be saved (it will only store the instance ID). The best way it to test it and use a Debug.Log() command to view the content.

Below you can see the content that will be saved to the JSON object.

Object for storing values to convert to JSON

Create a new Script to store the data that will be saved in the JSON file. In this example we have called the class / script PlayerStats.

This class contains three public variables[1]:

  • Vector3 playerPosition
  • string[9] playerName
  • Color playerColour

All of these have public visibility so that we can access them from outside of the class. These are created on lines 8, 10, and 12.

We also need[10] to make the class serializable by adding the [System[11].Serializable] attribute[2] on line 5.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class PlayerStats
{
    public Vector3 playerPosition;
    
    public string playerName;

    public Color playerColour;
}

Now we need to create another class / script. In this example it is called PlayerData_JSON

Attach the script to the EventSystem object (or any other suitable object).

Saving JSON Data

As we are going to be reading and writing files we need to import the System.IO library.

This is shown on line 4 below.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class PlayerData_JSON : MonoBehaviour
{
}

Create two functions. One for saving and one for loading the JSON. In this example they are SavePlayer() on lines 8-10 and LoadPlayer() on line 12-14 respectively.

On line 9 and 13 we create an empty / default PlayerStats object using the class we previously created. This is the object that we will be converting to a JSON object.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class PlayerData_JSON : MonoBehaviour
{
    public void SavePlayer() {
		PlayerStats ps = new PlayerStats();
    }

    public void LoadPlayer() {
		PlayerStats ps = new PlayerStats();
    }
}

First we need to get access to the data we want to save. In this example it is the player object. Depending on what data you want to save it might be in different objects.

We add GameObject player = GameObject.FindGameObjectWithTag(“Player”); to the save and load function[3].

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class PlayerData_JSON : MonoBehaviour
{
    public void SavePlayer() {
    	PlayerStats ps = new PlayerStats();
        GameObject player = GameObject.FindGameObjectWithTag("Player");
    }

    public void LoadPlayer() {
    	PlayerStats ps = new PlayerStats();
        GameObject player = GameObject.FindGameObjectWithTag("Player");
    }
}

Now we want to save the data.

First we need to assign the data to be saved to the variables in the PlayerStats object (the one we want to save).

To assign the player position to the variable[4] in PlayerStats we access the variable using the object name, in this case[12] ps. We then use dot notation to access the playerPosition property. We then assign the position located at player.transform.position to this variable / property as shown on line 11.

We repeat this for the other values. This example hard codes values into this as shown on lines 12 and 13.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class PlayerData_JSON : MonoBehaviour
{
   public void SavePlayer() {
    	PlayerStats ps = new PlayerStats();
        GameObject player = GameObject.FindGameObjectWithTag("Player");
        ps.playerPosition = player.transform.position;
        ps.playerColour = Color.black;
        ps.playerName = "Learn ICT Now";
    }

    public void LoadPlayer() {
        PlayerStats ps = new PlayerStats();
        GameObject player = GameObject.FindGameObjectWithTag("Player");
    }
}

Unity has a built in tool JsonUtility that can convert objects to a json string.

The code on line 15 will convert the object ps to a json string.

Note that the method ToJson() takes one argument which is the object that you want to convert.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class PlayerData_JSON : MonoBehaviour
{
    public void SavePlayer() {
    	PlayerStats ps = new PlayerStats();
        GameObject player = GameObject.FindGameObjectWithTag("Player");
        ps.playerPosition = player.transform.position;
        ps.playerColour = Color.black;
        ps.playerName = "Learn ICT Now";
        
        string jsonData = JsonUtility.ToJson(ps);
    }

    public void LoadPlayer() {
        PlayerStats ps = new PlayerStats();
        GameObject player = GameObject.FindGameObjectWithTag("Player");
    }
}

It is a good idea to use a Debug.Log to print out the JSON data as shown on line 17.

Line 19 uses the C# file writer to write the text to a file.

Application.persistentDataPath will get the default unity folder for saving data. We append the filename to this.

Note that there is a / infront of the filename which is used to separate the directory and the file.

Make sure to include a file extension like .json to make the files easier to manage. This is good file management techniques.

The second argument for the WriteAllText method is the data to be written. In this example it is the string jsonData.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class PlayerData_JSON : MonoBehaviour
{
    public void SavePlayer() {
        PlayerStats ps = new PlayerStats();
        GameObject player = GameObject.FindGameObjectWithTag("Player");

        ps.playerPosition = player.transform.position;
        ps.playerColour = Color.black;
        ps.playerName = "Learn ICT Now";
        
        string jsonData = JsonUtility.ToJson(ps);
        Debug.Log("JSON: " + jsonData);
        
        File.WriteAllText( Application.persistentDataPath + "/savefile.json" , jsonData );
    }

    public void LoadPlayer() {
        PlayerStats ps = new PlayerStats();
        GameObject player = GameObject.FindGameObjectWithTag("Player");
    }
}

We have now saved the data to a file.

Make a build (can’t be WebGL).

Run the build.

Press the save button. See Linking Buttons below for more information.

Go to the appropriate default persistant data folder. Find out it’s location here.

You should see the saved json file.

Load JSON Data

We first need to read in the data and convert it to the type of object that we want.

On line 26 we create a string to store the path where the json file is to be loaded from.

Line 27 will read in the data from the file and store it in a string to be processed later. In this example this variable is called jsonData.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class PlayerData_JSON : MonoBehaviour
{
    public void SavePlayer() {
        PlayerStats ps = new PlayerStats();
        GameObject player = GameObject.FindGameObjectWithTag("Player");

        ps.playerPosition = player.transform.position;
        ps.playerColour = Color.black;
        ps.playerName = "Learn ICT Now";

        string jsonData = JsonUtility.ToJson(ps);
        Debug.Log("JSON: " + jsonData);
        
        File.WriteAllText( Application.persistentDataPath + "/savefile.json" , jsonData );
    }

    public void LoadPlayer() {
        PlayerStats ps = new PlayerStats();
        GameObject player = GameObject.FindGameObjectWithTag("Player");

        string jsonFileLocation = Application.persistentDataPath + "/savefile.json";
        string jsonData = File.ReadAllText(jsonFileLocation);
    }
}

We will overwrite the ps (PlayerStats) object by reading it from the jsonData string. We achieve this by using the FromJson method of JsonUtility.

We set the type of object by putting the name / type of the class inside the < > brackets.

This is shown on line 29

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class PlayerData_JSON : MonoBehaviour
{
    public void SavePlayer() {
        PlayerStats ps = new PlayerStats();
        GameObject player = GameObject.FindGameObjectWithTag("Player");

        ps.playerPosition = player.transform.position;
        ps.playerColour = Color.black;
        ps.playerName = "Learn ICT Now";

        string jsonData = JsonUtility.ToJson(ps);
        Debug.Log("JSON: " + jsonData);
        
        File.WriteAllText( Application.persistentDataPath + "/savefile.json" , jsonData );
    }

    public void LoadPlayer() {
        PlayerStats ps = new PlayerStats();
        GameObject player = GameObject.FindGameObjectWithTag("Player");

        string jsonFileLocation = Application.persistentDataPath + "/savefile.json";
        string jsonData = File.ReadAllText(jsonFileLocation);

        ps = JsonUtility.FromJson<PlayerStats>(jsonData);

    }
}

Now we will process the data.

Line 31 will print the player name to the console. This is for testing[5].

To update the player object we need to first disable it (line 32) and finally reenable it (line 35).

Line 33 takes the Vecto3 position from the json object and then updates the position of the players transform.

Line 34 will get the renderer for a child of the player object (in this case the body of the player) and then sets the material to the player colour stored in the json object.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class PlayerData_JSON : MonoBehaviour
{
    public void SavePlayer() {
        PlayerStats ps = new PlayerStats();
        GameObject player = GameObject.FindGameObjectWithTag("Player");

        ps.playerPosition = player.transform.position;
        ps.playerColour = Color.black;
        ps.playerName = "Learn ICT Now";

        string jsonData = JsonUtility.ToJson(ps);
        Debug.Log("JSON: " + jsonData);
        
        File.WriteAllText( Application.persistentDataPath + "/savefile.json" , jsonData );
    }

    public void LoadPlayer() {
        PlayerStats ps = new PlayerStats();
        GameObject player = GameObject.FindGameObjectWithTag("Player");

        string jsonFileLocation = Application.persistentDataPath + "/savefile.json";
        string jsonData = File.ReadAllText(jsonFileLocation);

        ps = JsonUtility.FromJson<PlayerStats>(jsonData);

        Debug.Log("Player name: " + ps.playerName);
        player.SetActive(false);
        player.transform.position = ps.playerPosition;
        player.GetComponentInChildren<Renderer>().material.color = ps.playerColour;
        player.SetActive(true);
    }
}

Testing

Run the program and click Save.

Move the player.

Click the Load button.

Linking Buttons

Make sure that you link up the buttons to the script.

As the script is attached to EventSystem we can load the object from the scene. Choose the appropriate method.

Completed Code

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class PlayerStats
{
    public Vector3 playerPosition;
    
    public string playerName;

    public Color playerColour;
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class PlayerData_JSON : MonoBehaviour
{
    public void SavePlayer() {
        PlayerStats ps = new PlayerStats();
        GameObject player = GameObject.FindGameObjectWithTag("Player");

        ps.playerPosition = player.transform.position;
        ps.playerColour = Color.black;
        ps.playerName = "Learn ICT Now";

        string jsonData = JsonUtility.ToJson(ps);

        Debug.Log("JSON: " + jsonData);
        
        File.WriteAllText( Application.persistentDataPath + "/savefile.json" , jsonData );
    }

    public void LoadPlayer() {
        PlayerStats ps = new PlayerStats();
        GameObject player = GameObject.FindGameObjectWithTag("Player");

        string jsonFileLocation = Application.persistentDataPath + "/savefile.json";
        string jsonData = File.ReadAllText(jsonFileLocation);

        ps = JsonUtility.FromJson<PlayerStats>(jsonData);

        Debug.Log("Player name: " + ps.playerName);
        player.SetActive(false);
        player.transform.position = ps.playerPosition;
        player.GetComponentInChildren<Renderer>().material.color = ps.playerColour;
        player.SetActive(true);
    }
}
Terms definitions
2. attribute. A characteristic that describes either a function (something that a thing does) or a physical aspect (something that a thing is). Sometimes you might hear the word metadata to refer to an attribute, meaning that it isn’t the data itself, but the thing that describes the data
3. function. A named section of a computer program that performs a specific task. Functions help make code more efficient and reusable. They may take input parameters and produce output.
4. variable. A placeholder for a piece of information that can change.
5. testing. Testing[5] is about confirming decisions and ensuring that the desired result is produced form and outcome[18] or component[14].
Terms definitions
1. variables. Length: 60 minutes|Difficulty: Hard There is no live WebGL demo for this example due to it requiring saving and loading of data. Use the previous lesson to help with setting up Save and Load buttons on the scene. We are going to create a class that will be saved as a JSON (JavaScript Object Notation) […]
2. attribute. Length: 60 minutes|Difficulty: Hard There is no live WebGL demo for this example due to it requiring saving and loading of data. Use the previous lesson to help with setting up Save and Load buttons on the scene. We are going to create a class that will be saved as a JSON (JavaScript Object Notation) […]
3. function. Length: 60 minutes|Difficulty: Hard There is no live WebGL demo for this example due to it requiring saving and loading of data. Use the previous lesson to help with setting up Save and Load buttons on the scene. We are going to create a class that will be saved as a JSON (JavaScript Object Notation) […]
4. variable. Length: 60 minutes|Difficulty: Hard There is no live WebGL demo for this example due to it requiring saving and loading of data. Use the previous lesson to help with setting up Save and Load buttons on the scene. We are going to create a class that will be saved as a JSON (JavaScript Object Notation) […]
5. testing. Length: 60 minutes|Difficulty: Hard There is no live WebGL demo for this example due to it requiring saving and loading of data. Use the previous lesson to help with setting up Save and Load buttons on the scene. We are going to create a class that will be saved as a JSON (JavaScript Object Notation) […]