1
\$\begingroup\$

I've been at this for two long days, trying to figure out a way to handle saving and loading data for a mobile puzzle game I'm working on. The game has level packs and each level pack has multiple levels. Level packs can be purchased via IAP.

I need access to the fixed data and an easy way to add new data, so I used Scriptable Objects to store my Level Pack and Level data:

LevelPack

  • id
  • type (paid, free)
  • IAPproductId
  • levels (list of levels)

Level

  • id
  • totalTime (total time to solve puzzle)
  • warningTime (give a warning during level)
  • levels (list of levels)

I also need to store the following dynamic data, which can change during runtime:

  • completedTime for each level
  • isPurchased for each level pack

I tried adding these to the scriptable objects and it's really easy to maintain and work with. But I can't figure out a way to persist that data between sessions or game updates.

I created a save system that saves just the completed levels to an external file using binary formatter. So every time a level is completed, I change Level.completedTime to the new time on the scriptable object, then do GameState.Instance.UpdateLevelProgress(levelPack.id, levelId, time); to save the file that contains a dictionary with all the completed times.

 public void UpdateLevelProgress(int levelPackId, int levelId, float time)
     {
         if (!levelProgress.ContainsKey(levelPackId))
         {
             levelProgress.Add(levelPackId, new Dictionary<int, float>());
         }
 
         // set time
         levelProgress[levelPackId][levelId] = time;
 
         // save to file
         SaveGame();
     }

Even though I figured out how to save, I don't know how to load the data from the file into each Level scriptable object. There is an onEnable function for ScriptableObject, but that runs way before my GameState.LoadGame() function that runs inside a preload scene. So GameState.Instance.levelProgress is empty at that point.

Is there a better, cleaner way to do this?

PS:

Here's my GameState file:

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

[System.Serializable]
public class GameStateData
{
    public Dictionary<int, Dictionary<int, float>> levelProgress;

    public GameStateData(GameState gameState)
    {
        levelProgress = gameState.levelProgress;
    }
}

public class GameState : Singleton<GameState>
{
      

    // Prevent non-singleton constructor use
    protected GameState() {  }

    // declare variables
    public Dictionary<int, Dictionary<int, float>> levelProgress = new Dictionary<int, Dictionary<int, float>>();



    #region Load / Save Methods
    public void LoadGame()
    {

        string path = Path.Combine(Application.persistentDataPath, "game.save");

        if (File.Exists(path))
        {
            BinaryFormatter formatter = new BinaryFormatter();
            FileStream stream = new FileStream(path, FileMode.Open);

            GameStateData data = formatter.Deserialize(stream) as GameStateData;
            stream.Close();

            // overwrite current data with loaded data
            levelProgress = data.levelProgress;
        }
    }

    public void SaveGame()
    {
        BinaryFormatter formatter = new BinaryFormatter();

        string path = Path.Combine(Application.persistentDataPath, "game.save");
        FileStream stream = new FileStream(path, FileMode.Create);

        GameStateData data = new GameStateData(this);

        formatter.Serialize(stream, data);
        stream.Close();
    }
    #endregion

    public void UpdateLevelProgress(int levelPackId, int levelId, float time)
    {
        if (!levelProgress.ContainsKey(levelPackId))
        {
            levelProgress.Add(levelPackId, new Dictionary<int, float>());
        }

        // set time
        levelProgress[levelPackId][levelId] = time;

        // save to file
        SaveGame();
    }

}

```
\$\endgroup\$
7
  • \$\begingroup\$ I think you are looking for docs.unity3d.com/ScriptReference/…, I'm afraid its not something I have much experience with but I'm aware, OnAfterDeserialize and OnBeforeSerialize are used in a few projects i've seen to load * and save ScriptableObjects with data from a json file. Better documentation docs.unity3d.com/ScriptReference/… \$\endgroup\$ Commented Jan 22, 2021 at 20:40
  • 1
    \$\begingroup\$ I assume you tried having your GameState.LoadGame() function iterate over a list of your level packs and pass the loaded level time data to them? Or having it cache that data in a place the level pack can read it just before you try to display the saved time value? Help us understand where this needs to fit in your code and what specific obstacle you need help overcoming. \$\endgroup\$ Commented Jan 22, 2021 at 20:55
  • \$\begingroup\$ @Pheonix2105 do they read directly from the file inside OnBeforeSerialize? That would be the only way this could work. So no GameState singleton. I have multiple Levels, so that would mean it would open that file that many times. Would that affect performance? \$\endgroup\$ Commented Jan 22, 2021 at 20:58
  • \$\begingroup\$ @DMGregory I edited my question and added the full GameState script. Should I just remove the variables off the GameState and have LoadGame update all the ScriptableObjects instead based on the data I have loaded? \$\endgroup\$ Commented Jan 22, 2021 at 21:07
  • 1
    \$\begingroup\$ I think you might be in a better position to post an answer, since you can share the actual code that solved your problem. I've got all the rep I need, so I don't mind at all. 😉 \$\endgroup\$ Commented Jan 23, 2021 at 16:55

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.