17

I find the best way to save game data in Unity3D Game engine.
At first, I serialize objects using BinaryFormatter.

But I heard this way has some issues and is not suitable for save.
So, What is the best or recommended way for saving game state?

In my case, save format must be byte array.

shA.t
  • 16,580
  • 5
  • 54
  • 111
Sizzling
  • 215
  • 1
  • 3
  • 7

2 Answers2

42

But I heard this way has some issues and not suitable for save.

That's right. On some devices, there are issues with BinaryFormatter. It gets worse when you update or change the class. Your old settings might be lost since the classes non longer match. Sometimes, you get an exception when reading the saved data due to this.

Also, on iOS, you have to add Environment.SetEnvironmentVariable("MONO_REFLECTION_SERIALIZER", "yes"); or you will have problems with BinaryFormatter.

The best way to save is with PlayerPrefs and Json. You can learn how to do that here.

In my case, save format must be byte array

In this case, you can convert it to json then convert the json string to byte array. You can then use File.WriteAllBytes and File.ReadAllBytes to save and read the byte array.

Here is a Generic class that can be used to save data. Almost the-same as this but it does not use PlayerPrefs. It uses file to save the json data.

DataSaver class:

public class DataSaver
{
    //Save Data
    public static void saveData<T>(T dataToSave, string dataFileName)
    {
        string tempPath = Path.Combine(Application.persistentDataPath, "data");
        tempPath = Path.Combine(tempPath, dataFileName + ".txt");

        //Convert To Json then to bytes
        string jsonData = JsonUtility.ToJson(dataToSave, true);
        byte[] jsonByte = Encoding.ASCII.GetBytes(jsonData);

        //Create Directory if it does not exist
        if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
        {
            Directory.CreateDirectory(Path.GetDirectoryName(tempPath));
        }
        //Debug.Log(path);

        try
        {
            File.WriteAllBytes(tempPath, jsonByte);
            Debug.Log("Saved Data to: " + tempPath.Replace("/", "\\"));
        }
        catch (Exception e)
        {
            Debug.LogWarning("Failed To PlayerInfo Data to: " + tempPath.Replace("/", "\\"));
            Debug.LogWarning("Error: " + e.Message);
        }
    }

    //Load Data
    public static T loadData<T>(string dataFileName)
    {
        string tempPath = Path.Combine(Application.persistentDataPath, "data");
        tempPath = Path.Combine(tempPath, dataFileName + ".txt");

        //Exit if Directory or File does not exist
        if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
        {
            Debug.LogWarning("Directory does not exist");
            return default(T);
        }

        if (!File.Exists(tempPath))
        {
            Debug.Log("File does not exist");
            return default(T);
        }

        //Load saved Json
        byte[] jsonByte = null;
        try
        {
            jsonByte = File.ReadAllBytes(tempPath);
            Debug.Log("Loaded Data from: " + tempPath.Replace("/", "\\"));
        }
        catch (Exception e)
        {
            Debug.LogWarning("Failed To Load Data from: " + tempPath.Replace("/", "\\"));
            Debug.LogWarning("Error: " + e.Message);
        }

        //Convert to json string
        string jsonData = Encoding.ASCII.GetString(jsonByte);

        //Convert to Object
        object resultValue = JsonUtility.FromJson<T>(jsonData);
        return (T)Convert.ChangeType(resultValue, typeof(T));
    }

    public static bool deleteData(string dataFileName)
    {
        bool success = false;

        //Load Data
        string tempPath = Path.Combine(Application.persistentDataPath, "data");
        tempPath = Path.Combine(tempPath, dataFileName + ".txt");

        //Exit if Directory or File does not exist
        if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
        {
            Debug.LogWarning("Directory does not exist");
            return false;
        }

        if (!File.Exists(tempPath))
        {
            Debug.Log("File does not exist");
            return false;
        }

        try
        {
            File.Delete(tempPath);
            Debug.Log("Data deleted from: " + tempPath.Replace("/", "\\"));
            success = true;
        }
        catch (Exception e)
        {
            Debug.LogWarning("Failed To Delete Data: " + e.Message);
        }

        return success;
    }
}

USAGE:

Example class to Save:

[Serializable]
public class PlayerInfo
{
    public List<int> ID = new List<int>();
    public List<int> Amounts = new List<int>();
    public int life = 0;
    public float highScore = 0;
}

Save Data:

PlayerInfo saveData = new PlayerInfo();
saveData.life = 99;
saveData.highScore = 40;

//Save data from PlayerInfo to a file named players
DataSaver.saveData(saveData, "players");

Load Data:

PlayerInfo loadedData = DataSaver.loadData<PlayerInfo>("players");
if (loadedData == null)
{
    return;
}

//Display loaded Data
Debug.Log("Life: " + loadedData.life);
Debug.Log("High Score: " + loadedData.highScore);

for (int i = 0; i < loadedData.ID.Count; i++)
{
    Debug.Log("ID: " + loadedData.ID[i]);
}
for (int i = 0; i < loadedData.Amounts.Count; i++)
{
    Debug.Log("Amounts: " + loadedData.Amounts[i]);
}

Delete Data:

DataSaver.deleteData("players");
Community
  • 1
  • 1
Programmer
  • 121,791
  • 22
  • 236
  • 328
  • Thanks! Because our project requires mono2x setting, We failed that Environment setting. I'll try to JsonUtility and your comment! – Sizzling Dec 05 '16 at 06:31
  • Hi, I used your DataSaver but it doesn't seem to save a dictionary? I checked the local file. Every variable and its value are saved but not for my Dictionary. I thought it might be display problem but when I load it the dict variable is still null. – Seaky Lone Mar 24 '18 at 21:04
  • @SeakyLone That's right. It doesn't save `Dictionary`. This is because Unity's `JsonUtility` cannot serialize dictionary. I suggest you write an extension that flattens the dictionary into array or List then serialize/de-serialize that. If you can't do that then use [this](https://github.com/SaladLab/Json.Net.Unity3D) version of `Newtonsoft.Json` made for Unity instead of `JsonUtility` in the code and you should be able to serialize/de-serialize `Dictionary`. – Programmer Mar 24 '18 at 23:49
  • Could you please tell me how to install the Newtonsoft.Json in the link that you provided? I have read the readme several times but I still couldn't figure it out. I copied the link.xml but it's still not working. – Seaky Lone May 25 '18 at 22:30
  • @SeakyLone Go to the link I posted above then scroll down and look at the **Where can I get it?** section. It has a release link which you can go and then download it as a Unity package then import into Unity – Programmer May 25 '18 at 22:36
  • 1
    Thank you so much!!! I thought I needed to download that repo and I was like how could that work. Thank you!! – Seaky Lone May 25 '18 at 22:45
  • BTW I seem to find a problem with the Newtonsoft.Json, it can serialize Vector2int into "(x, y)" but can't deserialize it. – Seaky Lone May 25 '18 at 23:16
  • Actually I used Vector2Int as my dictionary key and this problem seems to be Vector2Int is too complicated as a key and I need to write a converter thing. I am working on this. – Seaky Lone May 26 '18 at 00:07
  • 3
    I realize its been like 3 years, but do not use PlayerPrefs to store game state. Use regular file IO. – Draco18s no longer trusts SE Oct 27 '19 at 17:32
3

I know this post is old, but in case other users also find it while searching for save strategies, remember:

PlayerPrefs is not for storing game state. It is explicitly named "PlayerPrefs" to indicate its use: storing player preferences. It is essentially plain text. It can easily be located, opened, and edited by any player. This may not be a concern for all developers, but it will matter to many whose games are competitive.

Use PlayerPrefs for Options menu settings like volume sliders and graphics settings: things where you don't care that the player can set and change them at will.

Use I/O and serialization for saving game data, or send it to a server as Json. These methods are more secure than PlayerPrefs, even if you encrypt the data before saving.