Saving and Loading

In this tutorial, you will learn how to save and load game information

Reasons to Save and Load


Not all games need the ability to save and load. If your game is very short and the player can finish it in 5-10 minutes, you may not need the ability save and load the game.

But if your game is meant to be played over a longer period, or you just want to give the player the ability to go back to a previous point if they lose, then you can consider adding a save and load system.

In this lesson, you will be adding a save and load system to a basic 3D adventure game with platforms. You can also follow along with the code instructions in your own project, if you want to. The starting template for the project is here: Saving and Loading Base Project.


The Game

In this game, the player jumps from platform to platform, and there are various checkpoints along their path. In the save and load system for this game, the player's progress will save when they reach a checkpoint, and they can load the game from that checkpoint when after they exit the game or reach the game over screen.

Cannot Load Image

There are two levels. At the end of the first level, the door will take them to the second level, and at the end of the second level, the player will be taken to the win screen.

Cannot Load Image

If the player falls off the edge of the world, they will be taken to the game over screen. The first level is forgiving if the player makes a mistake in their jump, they can just start from the beginning. However, the second level has more perilous jumps.

Go ahead and try to play through the project after you download it. You might notice that the Load Game buttons don't work yet. You will be enabling these buttons when you add the save and load functionality to the game.

Data Saving Levels



Before you create a save system, you need to understand the different levels

Data Save Level Where It Can Be Found When It Gets Reset
GameObject-attached Scripts (Monobehaviors) Scenes When the player loads a new scene
ScriptableObjects Session When the player exits the exported game application
Unity PlayerPrefs Computer Hard Drive If the data is reset from the code or the player deletes the file on their filesystem


We will use both Scriptable Objects and the Player Prefs file to create our save-load system. When the player saves the game, changes will be stored in both the ScriptableObject and the PlayerPrefs file.

When the player wants to load the game INSIDE of a session, information will be pulled from the ScriptableObject.

When the game starts, the game will check to see if the player has save information stored in PlayerPrefs. If they do, then it will load the data into the ScriptableObject, and allow them to load the checkpoint that was saved.

There are other ways to save data, such as creating your own filesystem storage system not using the Unity PlayerPrefs system, or saving players' data in the cloud. In this lesson, we'll stick to using the three levels of data saving in the table above.

Save ScriptableObject Code


Create a new script in the scripts folder called SaveInformation. This will be the ScriptableObject that will save the player's information across all the different scenes of the game.

In addition to storing information, ScriptableObjects can have methods that can be called by scripts inside of the scene. To keep the save system consistent across all the scenes, the methods for saving and loading the game will exist on this ScriptableObject.

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

[CreateAssetMenu]
public class SaveInformation : ScriptableObject
{
    public string saveScene;
    public string saveCheckpoint;

    public void saveGameData(string sceneToSave, string checkpointToSave)
    {
        saveScene = sceneToSave;
        saveCheckpoint = checkpointToSave;
    }

    public void deleteSaveData()
    {
        saveScene = null;
        saveCheckpoint = null;
    }

    public void loadSavedScene()
    {
        SceneManager.LoadScene(saveScene);
    }

}


This script inherits from the ScriptableObject class instead of the Monobehavior class. This means that this script cannot be attached to a GameObject inside of a scene, but exists independently of any scene.

Because this script is a ScriptableObject, make sure that you create an instance of SaveInformation inside of your project. Create a new folder called ScriptableObjects and using the right-click context menu to create an instance of SaveInformation. This instance will be connected to scripts inside of scenes, since those objects will need to specify when to save data.



Go to the TitleScreen scene and find the LoadGameButton under the Canvas in the hierarchy. When the button is clicked, set the button to call the loadSavedScene method from the SaveInformation ScriptableObject. Then, do the same for the GameOverScreen scene.



You can manually type in a scene name into the SaveInformation object and have it load the scene when the player presses the Load Game button, but we still need to add the code that will save the player's information when they reach a checkpoint. From the title scene, try manually entering in Level02 and see that the button takes you to the second level.



Because SaveInformation is a ScriptableObject, the values in the variables are saved across scenes. This means that whenever you press New Game, the value in the save isn't replaced. When the player starts a new game, it should erase their save data.

Add a new action to the New Game button to call the SaveInformation object's deleteSaveData method.



Try pressing the New Game button now, and you will see that the saved scene data will have been cleared out when you started the new game.



Checkpoint Code


Now you can start to set up the checkpoint system that will save into the SaveInformation object. Create a new script called Checkpoint. This script will be used by the checkpoint object to save information when the player enters the checkpoint's trigger.

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

public class Checkpoint : MonoBehaviour
{
    public string checkpointName;
    public SaveInformation saveObject;
    public MeshRenderer checkpointIndicator;
    public Material checkpointActiveMaterial;
    public Transform playerSpawnPosition;

    private void Start()
    {
        if (saveObject.saveScene == SceneManager.GetActiveScene().name 
            && saveObject.saveCheckpoint == checkpointName)
        {
            GameObject player = GameObject.FindGameObjectWithTag("Player");
            player.transform.position = playerSpawnPosition.transform.position;
        }
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.tag == "Player")
        {
            saveObject.saveGameData(SceneManager.GetActiveScene().name, checkpointName);
			checkpointIndicator.material = checkpointActiveMaterial;
        }
    }
}


Variables

checkpointName: If a scene has more than 1 checkpoint, it is important to include a unique checkpoint name so that the player will return to the correct checkpoint inside of the scene.

saveObject: A reference to the SaveInformation object, so that the checkpoint can call that object's saveGameData method

checkpointIndicator: This will be the PoleTop object inside of the Checkpoint Prefab. When the player activates the checkpoint, the material will be changed to give the player an indication their game has saved.

checkpointActiveMaterial: The material to replace the original material, this will indicate the player has reached the checkpoint

playerSpawnPosition: This transform can be set to indicate where the player will spawn. If you don't want the player to spawn directly on top of the checkpoint, you can always put the spawn position somewhere else.


Methods

Start: In the Start method, the checkpoint checks the scene and the checkpoint saved in the SaveInformation object. If the scene matches the current scene, and the checkpoint matches this checkpoint name, then the player object will be found through a tag and moved to the playerSpawnPosition transform.

OnTriggerEnter: If the player enters the trigger for this GameObject, the saveGameData method will be called on the SaveInformation object with the current scene and current checkpoint name. Then, the material on the checkpointIndicator will be changed.


Go to the Level01 scene and find the checkpoint object on the scene. Attach the Checkpoint script to the GameObject, and then make sure that the references on the checkpoint script are set up.

For the checkpoint name, use a name that provides some insight into the checkpoint. For this example, you can call this checkpoint Midpoint.



Since this is a prefab, and many of these connections will be the same across all prefabs. Override the prefab to apply these changes to all the prefabs that were placed in Level02. This will make all objects which were created from this prefab have the same values for their variables. Then, go to Level02 and change the checkpointName variables to be unique on both of the checkpoint prefabs located in that scene. You can call one First and the other Second.



Now you can test out your game. Start from the title screen, and reach the first checkpoint and then jump off the edge of the world. You can now press the Load Game button on the game over screen to load the game from that checkpoint.



PlayerPrefs



Right now the save information will stay for the entire Unity Editor session, but to take the save system to the next level, you can enable the game to save information even when the player exits out of the game.

Go to the SaveInformation code and add lines related to PlayerPrefs.

    public void saveGameData(string sceneToSave, string checkpointToSave)
    {
        saveScene = sceneToSave;
        saveCheckpoint = checkpointToSave;
        PlayerPrefs.SetString("savedScene", saveScene);
        PlayerPrefs.SetString("savedCheckpoint", checkpointToSave);
        PlayerPrefs.Save();
    }

    public void deleteSaveData()
    {
        PlayerPrefs.DeleteKey("savedScene");
        PlayerPrefs.DeleteKey("savedCheckpoint");
        PlayerPrefs.Save();
        saveScene = null;
        saveCheckpoint = null;
    }
    
    //Returns true if a game was loaded, false if there is no save
    public bool loadGameData()
    {
        if (PlayerPrefs.HasKey("savedScene"))
        {
            saveScene = PlayerPrefs.GetString("savedScene");
            saveCheckpoint = PlayerPrefs.GetString("savedCheckpoint");
            return true;
        } else
        {
            return false;
        }
    }
    
    public void loadSavedScene()
    {
        SceneManager.LoadScene(saveScene);
    }


Now, every time the saveGameData method is called, in addition to updating information inside of the SaveInformation object, the values will be saved into PlayerPrefs.

In the deleteSaveData method, the code now deletes the saved values in addition to removing values from the SaveInformation object.

There is one new method, loadGameData. This method checks to see if there is information stored inside of PlayerPrefs. If there is data, it stores that information into the ScriptableObject and returns true. Otherwise, it returns false, as it attempted to load data without any result.

ScriptableObjects are powerful because they can store data across scenes. However, there is one problem with using them: you can't use Start or Update with ScriptableObjects, so you will need to create another script to call SaveInformation to load data from PlayerPrefs.

Create a script called LoadGameStart. This script will be attached to the Load Game button.

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

public class LoadGameStart : MonoBehaviour
{
    public SaveInformation saveObject;

    void Start()
    {
        Button loadGameButton = GetComponent<Button>();
        bool saveFileExists = saveObject.loadGameData();
        if (!saveFileExists)
        {
            loadGameButton.interactable = false;
        }
    }
    
}


This script will call the loadGameData method on the SaveInformation object. If there is no save file, then it will disable the button so that the player can't accidentally click it.

Attach this script to the Load Game buttons on the title screen and the game over screen, and connect the scripts to the SaveInformation object.



Test out the game by starting a new game and jumping off the edge of the world. The game over screen will have the Load Game button disabled. Then, try to reach a checkpoint and reach the game over screen. You can now see the load button and press it to return to your original checkpoint.



You can also test the exported game to make sure that the player's progress is saved even when they quit the game.



Design Considerations

While you're designing your game, here's some things to keep in mind about checkpoints and saving.


Checkpoints: Reward or Tension Builder

Players can see checkpoints one of two ways, as a stress reliever, or stress builder.

If you put a checkpoint at the end of a really tough section of your game, then the player will feel a sense of release since they know they won't have to play that section again.

However, if you put a checkpoint after a long and easy section of the game, the player will think that there is about to be a really difficult section up ahead. They will feel their tension rise in anticipation of the big moment.

Try to use checkpoints to manage the player's tension level. You don't want the player to be so tense as to throw their controller in frustration, but you don't want to include checkpoints everywhere and have the player not feel like any of their actions matter because they can easily restart their game.


Save Anywhere vs. Checkpoints

In some cases, rather than having the player's progress saved when they reach a checkpoint, you might want to create an option where they open a pause menu and can save their progress.

It's very easy to save the player's position in the world, you will just need to save the X, Y, and Z values of the player's Transform and set the player at that exact world position when the game is loaded.

However, if you let the player save whenever they want, they might forget to save, and feel frustrated when they have to go a long way to get back to where they were. Or, they might save too often, and it will break up the challenge you're trying to create for them.

You will need to decide whether giving players the flexibility to save whenever they want will help or hurt the fun of the game.