Save/Load System Not Working

Developing my first game, and trying to get the save/load system working. I followed this tutorial, https://www.youtube.com/watch?v=aUi9aijvpgs, which got me started, but I’m running into some issues in the actual practice.

Initially, when following that tutorial, I had my game data (current level, progress stats, etc.) and player preferences (difficulty settings, audio/visual settings, etc.) being managed in one file data system, but I was having trouble getting the player preferences to carry over between sessions; ie, when you mute the music at the start of the game, it remains muted through that session, but when you exit the application and reopen, it returns to the default music volume setting instead of remembering that you wanted it muted.

At the moment, I only have the first level of the game completed, and for this game I’m only having it do an autosave at the end of each level as opposed to having a manual save system, so I wasn’t able to test whether the game data was saving or not, but I added in a “test” end of level, and confirmed that the previously completed game data is not loading in a new session either.

In my attempts to trouble shoot, I ended up splitting the game data and preferences into two separate file data systems by duplicating what I’d setup with the tutorial and splitting the data management accordingly, but unfortunately it’s still not loading the previous session’s settings when I start a new session, and I’m at a loss of what to try next.

In reviewing the debug log, it indicates that the preferences are being saved, but when I start a new session it indicates that there is no saved data to load. Part of my problem is that I’m not sure where the data files are being saved on the computer - in the Inspector, I’ve set the file names to save as “data.game” and “data.prefs” respectively (following the example from the tutorial), but I can’t find any files with either those in the program folders, so I guess it’s not actually generating those files…but I’m not sure what I did wrong, since I followed the tutorial as closely as I could.

I’m going to copy the sections of script that I believe are relevant here, but also attaching the full scripts in case there’s anything important I neglected to copy. Just going to show the scripts related to the Preferences Data, but the Game Data scripts are more or less the same:

  1. This is the Awake function from the Data Persistence Manager
private void Awake()
    {
        if (instance != null)
        {
            Debug.LogError("Data Persistence Manager: Found more than one Data Persistence Manager in the scene.");
            Destroy(this.gameObject);
            return;
        }
        instance = this;
        DontDestroyOnLoad(this.gameObject);

        if (disableDataPersistence)
        {
            Debug.LogWarning("Data Persistence Manager: Data Persistence is currently disabled!");
        }

        this.dataHandler = new FileDataHandler(Application.persistentDataPath, fileName, useEncryption);
        this.prefsDataHandler = new PrefsFileDataHandler(Application.persistentDataPath, prefsFileName, useEncryption);

        InitializeSelectedProfileId();
    }
  1. This is the InitializeSelectedProfileId function from the Data Persistence Manager:
private void InitializeSelectedProfileId()
    {
        this.selectedProfileId = dataHandler.GetMostRecentlyUpdatedProfileId();
        this.prefsSelectedProfileId = prefsDataHandler.GetMostRecentlyUpdatedProfileId();
        if (overrideSelectedProfileId)
        {
            this.selectedProfileId = testSelectedProfileId;
            this.prefsSelectedProfileId = prefsTestSelectedProfileId;
            Debug.LogWarning("Data Persistence Manager: Overrode selected profile id with test id: " + testSelectedProfileId + " and " + prefsSelectedProfileId);
        }
    }
  1. This is the LoadPrefs function from the Data Persistence Manager
public void LoadPrefs()
    {
        // return right away if data persistence is disabled
        if (disableDataPersistence)
        {
            return;
        }

        //Load Any Saved data from a file using the data handler
        this.prefsData = prefsDataHandler.Load(prefsSelectedProfileId);

        // if no data can be loaded, don't continue
        if (this.prefsData == null)
        {
            Debug.Log("Data Persistence Manager: No prefs data was found. A New Game needs to be started before data can be loaded.");
            return;
        }

        foreach (IDataPersistence dataPersistenceObj in dataPersistenceObjects)
        {
            dataPersistenceObj.LoadPrefs(prefsData);
        }

        Debug.Log("Data Persistence Manager: Loaded Show Collectibles on Map = " + prefsData.showCollectiblesOnMap);
        //Not copying all of the Debug Logs here, for ease of reading, but I rinse and repeat this last line for each of the variables being stored in the Prefs Data
  1. This is the SavePrefs function from the Data Persistence Manager:
public void SavePrefs()
    {
        // return right away if data persistence is disabled
        if (disableDataPersistence)
        {
            return;
        }

        // if we don't have any data to save, log a warning here
        if (this.prefsData == null)
        {
            this.prefsData = new PrefsData();
            Debug.Log("Data Persistence Manager: New Preferences Data File Created");
        }

        //pass the data to other scripts so they can update it
        foreach (IDataPersistence dataPersistenceObj in dataPersistenceObjects)
        {
            dataPersistenceObj.SavePrefs(prefsData);
        }

        Debug.Log("Data Persistence Manager: Saved Show Collectibles on Map = " + prefsData.showCollectiblesOnMap);
        //^^Rinse and repeat^^
    }

Any suggestions on what I’ve done wrong here would be greatly appreciated.

DataPersistenceManager.cs (25.5 KB)
IDataPersistence.cs (303 Bytes)
PrefsFileDataHandler.cs (9.9 KB)

… debugging?? It honestly just sounds like you wrote a bug… and that means… time to start debugging!

By debugging you can find out exactly what your program is doing so you can fix it.

Use the above techniques to get the information you need in order to reason about what the problem is.

You can also use Debug.Log(...); statements to find out if any of your code is even running. Don’t assume it is.

Once you understand what the problem is, you may begin to reason about a solution to the problem.

Remember with Unity the code is only a tiny fraction of the problem space. Everything asset- and scene- wise must also be set up correctly to match the associated code and its assumptions.

Here’s some more reading about the problem space:

Load/Save steps:

An excellent discussion of loading/saving in Unity3D by Xarbrough:

And another excellent set of notes and considerations by karliss_coldwild:

Loading/Saving ScriptableObjects by a proxy identifier such as name:

When loading, you can never re-create a MonoBehaviour or ScriptableObject instance directly from JSON. Save data needs to be all entirely plain C# data with no Unity objects in it.

The reason is they are hybrid C# and native engine objects, and when the JSON package calls new to make one, it cannot make the native engine portion of the object, so you end up with a defective “dead” Unity object.

Instead you must first create the MonoBehaviour using AddComponent() on a GameObject instance, or use ScriptableObject.CreateInstance() to make your SO, then use the appropriate JSON “populate object” call to fill in its public fields.

If you want to use PlayerPrefs to save your game, it’s always better to use a JSON-based wrapper such as this one I forked from a fellow named Brett M Johnson on github:

Do not use the binary formatter/serializer: it is insecure, it cannot be made secure, and it makes debugging very difficult, plus it actually will NOT prevent people from modifying your save data on their computers.

A good summary / survey of the problem space: