1

I am making a RPG in Unity. Still quite new so I followed their tutorial on creating save/load functionality.

In this tutorial they use 2 variables, and for that it works great. But I want to create a large game, with sandboxing. Which means Ill need to save the position of everything in my world, on top of that I want a lot of player stats, unlockables, quests, npc progress, and much more.

So far I have a working implementation for saving/loading my players initial stats, and that alone has turned in to a lot:

DataManager

public class DataManager : MonoBehaviour {

    private string saveLocation = "/data.dat";

    public static DataManager manager;

    //PLAYER
    public int maxHealth;
    public int currentHealth;
    public int currentLevel;
    public int currentExp;
    public int [] toLevelUp;

    void Awake(){
        if (manager == null) {
            DontDestroyOnLoad (gameObject);
            manager = this;
        } else if (manager != this) {
            Destroy (gameObject);
        }
    }


    public void Save(){
        Debug.Log ("Saving");
        BinaryFormatter bf = new BinaryFormatter ();
        FileStream file = File.Create (Application.persistentDataPath + saveLocation);

        PlayerData playerData = new PlayerData (maxHealth, currentHealth, currentLevel, currentExp, toLevelUp);

        bf.Serialize (file, playerData);
        file.Close ();
    }

    public void Load(){
        Debug.Log ("Loading");
        if (File.Exists (Application.persistentDataPath + saveLocation)) {
            BinaryFormatter bf = new BinaryFormatter ();
            FileStream file = File.Open (Application.persistentDataPath + saveLocation, FileMode.Open);

            PlayerData playerData = (PlayerData)bf.Deserialize (file);
            file.Close ();

            maxHealth = playerData.maxHealth;
            currentHealth = playerData.currentHealth;
            currentLevel = playerData.currentLevel;
            currentExp = playerData.currentExp;
            toLevelUp = playerData.toLevelUp;
        }
    }
}

PlayerData

[System.Serializable]
public class PlayerData {

    public int maxHealth;
    public int currentHealth;
    public int currentLevel;
    public int currentExp;
    public int [] toLevelUp;

    public PlayerData(int maxHealth, int currentHealth, int currentLevel, int currentExp, int [] toLevelUp){
        this.maxHealth = maxHealth;
        this.currentHealth = currentHealth;
        this.currentLevel = currentLevel;
        this.currentExp = currentExp;
        this.toLevelUp = toLevelUp;
    }

}

This does not feel very scaleable at all. Someone with experience implementing save/load for a lot of variables with any advice/code samples? There must be some better way?

EDIT:

New DataManager:

public class DataManager : MonoBehaviour {

private string saveLocation = "/data.dat";

public static DataManager manager;

//PLAYER
public PlayerData playerData = new PlayerData ();

void Awake(){
    if (manager == null) {
        DontDestroyOnLoad (gameObject);
        manager = this;
    } else if (manager != this) {
        Destroy (gameObject);
    }

}


public void Save(){
    Debug.Log ("Saving");
    BinaryFormatter bf = new BinaryFormatter ();
    FileStream file = File.Create (Application.persistentDataPath + saveLocation);

    bf.Serialize (file, playerData);
    file.Close ();
}

public void Load(){
    Debug.Log ("Loading");
    if (File.Exists (Application.persistentDataPath + saveLocation)) {
        BinaryFormatter bf = new BinaryFormatter ();
        FileStream file = File.Open (Application.persistentDataPath + saveLocation, FileMode.Open);

        PlayerData pd = (PlayerData)bf.Deserialize (file);
        file.Close ();

        playerData = pd;
    }
}

}

Green_qaue
  • 3,561
  • 11
  • 47
  • 89
  • You are doing it right. It's better to put the game data in another class that does not inherit from `MonoBehaviour` and you did just that. I am not a fan of `BinaryFormatter` due to problems it could cause so I just json or xml to save the game data. Check [this](https://stackoverflow.com/questions/40965645/what-is-the-best-way-to-save-game-state/40966346#40966346) wrapper I made for saving and loading any game data. – Programmer May 31 '17 at 15:30
  • @Programmer thanks, this class is gonna be a nightmare in a few months... – Green_qaue May 31 '17 at 15:34
  • I don't understand what you mean by nightmare.. If your class is huge, you can separate them into multiple classes. Very easy. By the way, you don't need the `PlayerData` constructor in your class. Just modify the public variables directly. – Programmer May 31 '17 at 15:36
  • @Programmer I mean that the `Load()`-function will turn into a nightmare. If I have hundreds of variables. having to assign all of them. The constructor is there to reduce the clutter in my `Save()`-method. – Green_qaue May 31 '17 at 15:47
  • No. I don't think you understand me. `PlayerData` should already be part of your game data. Instead of using local variables in your scrips, use the one from `PlayerData`. When you want to save it, you **don't** need to copy it. Just save the instance you are already using in your game. – Programmer May 31 '17 at 16:05
  • @Programmer Your right I don't really understand you. Could you post an answer with what you mean perhaps? Your saying I don't need to use local variable in `DataManager` and instead directly access the `PlayerData` class? So that in `Load()` I would just write `PlayerData playerData = (PlayerData)bf.Deserialize (file);` , and in `Save()`: `PlayerData playerData = new PlayerData (); bf.Serialize (file, playerData);` ? Just confused since ur first comment said I was doing it right :) – Green_qaue May 31 '17 at 16:12
  • @Programmer Maybe I got what u meant, I edited my answer, this definently reduces the code I need. Instead of using `DataManager.manager.currentExp` to alter Experience, I now use `DataManager.manager.playerData.currentExp`. – Green_qaue May 31 '17 at 16:22
  • Was in the process of writing answer before I saw your Edit. Anyways, check it out. Hopefully, that will make you understand what I am saying – Programmer May 31 '17 at 16:33

1 Answers1

3

You don't have to create new PlayerData each time you want to save it. You don't have to copy it like you did in the constructor. Create an instance of PlayerData then use it in your game. That's that. Remove those local variables because they are already in the PlayerData class.

Don't do this:

[System.Serializable]
public class PlayerData
{

    public int maxHealth;
    public int currentHealth;
    public int currentLevel;
    public int currentExp;
    public int[] toLevelUp;
}

//Why Why would you need these again?
public int maxHealth;
public int currentHealth;
public int currentLevel;
public int currentExp;
public int[] toLevelUp;

private string saveLocation = "/data.dat";

//PLAYER
public PlayerData playerData = new PlayerData();

public void Save()
{
    if (File.Exists(Application.persistentDataPath + saveLocation))
    {
        BinaryFormatter bf = new BinaryFormatter();
        FileStream file = File.Open(Application.persistentDataPath + saveLocation, FileMode.Open);

        PlayerData playerData = (PlayerData)bf.Deserialize(file);
        file.Close();

        //Whyyyyyyyyyyyy Don't do this
        maxHealth = playerData.maxHealth;
        currentHealth = playerData.currentHealth;
        currentLevel = playerData.currentLevel;
        currentExp = playerData.currentExp;
        toLevelUp = playerData.toLevelUp;
    }
}

Notice how you redefined the maxHealth, currentHealth, currentLevel, currentExp and toLevelUp variables again. Not necessary at-all.

Do this:

[System.Serializable]
public class PlayerData
{

    public int maxHealth;
    public int currentHealth;
    public int currentLevel;
    public int currentExp;
    public int[] toLevelUp;
}

string saveLocation = "/data.dat";

//Make the PlayerData data part of your game
PlayerData playerData;

void Start()
{

    playerData = new PlayerData();
    playerData.maxHealth = 100;
    playerData.currentLevel = 1;
    playerData.currentHealth = 50;
}

public void Save(PlayerData pData)
{
    UnityEngine.Debug.Log("Saving");
    BinaryFormatter bf = new BinaryFormatter();
    FileStream file = File.Create(Application.persistentDataPath + saveLocation);
    bf.Serialize(file, pData);
    file.Close();
}

When you want to save, just call Save(playerData);.

Notice how there is no need to copy the data or do PlayerData playerData = new PlayerData (maxHealth, currentHealth, currentLevel, currentExp, toLevelUp);. Just save the instance you are already using in your game.


Finally, with DataSaver, you can do this in one line DataSaver.saveData(playerData, "players");

Programmer
  • 121,791
  • 22
  • 236
  • 328
  • Perfect, thanks! If you send PlayerData as a parameter to save that will cause some issues in future when I need more Data Classes. Since if you want to save more than one DataObject all you do is add another line of `bf.Serialize(file, pData); bf.Serialize(file, someOtherData);` and load it like : `PlayerData pd = (PlayerData)bf.Deserialize (file); SomeOtherData sd = (SomeOtherDatasd )bf.Deserialize (file);` – Green_qaue May 31 '17 at 16:51
  • 1
    If you need more data-classes that are different class names, then look into **C# Generics**. Make the `Save` function a generic function so that it can take any class as parameter instead of just the `PlayerData `. This is what I did in my [`DataSaver`](https://stackoverflow.com/a/40966346/3785314) class. Please take a look at that. `` – Programmer May 31 '17 at 16:56
  • will try it with the DataSaver as well – Green_qaue May 31 '17 at 16:57
  • 1
    Think Ill be going with the DataSaver, very handy. Thanks! – Green_qaue May 31 '17 at 17:02
  • Ran into a problem with DataSaver, maybe there is an easy fix. I have multiple Items in my game, so I create a Class called Item and give it some properties. Now I want to save, say 15 items. So I create an Array or List of Items, and save them. However, your DataSave only works with single Objects as far as I can tell? I could make a workaround and loop through each Object in my Items-array and save, but cant do that when loading. Something you've run into? – Green_qaue Jun 01 '17 at 17:07
  • 1
    That's because Unity's `JsonUtility` [cannot](https://stackoverflow.com/a/36244111/3785314) serialize or deserialize arrays directly. See [this](https://codeshare.io/GL7pvp) post I made that describes two options you have to get around this. – Programmer Jun 01 '17 at 21:19