Unity

How to Save with Json Files in Unity + Nested Structure with List?

Technologies used in TETRY 3D part 3

Hello everyone, GE planet here.

In this article, I’ll show you how to save a large amount of data using Json instead of Unity’s save method, PlayersPrefs. There have been a number of similar articles already, but I’d like to share them with you so that you don’t forget them, and also so that you can use them in a more general way.

Contents

Structure

There are two types of saves used in TETRY 3D: one is an encrypted save in PlayersPrefs, oriented to smaller data, and the other is an encrypted save in PlayersPrefs, which is used for things like volume, music names and other settings that frequently change. This is used for frequently changing things like volume, music names and other settings.

The other one is written to a Json file, which is used to save the status of the game.

Let’s take a quick look at the Json save file.

The configuration file is as follows.

  • SaveManager.cs
  • SaveData.cs
  • SingletonMonoBehaviour.cs

Of these, “SingletonMonoBehaviour” is

It is explained in We have used it as is.

A singleton is an object that has only one, and is available in Unity without Find or GetCompornent.

SaveManager” inherits and uses this.

SaveManager

First, let’s look at what’s inside.

using System.Collections.Generic;
using System.IO;
using UnityEngine;

public class SaveManager : SingletonMonoBehaviour<SaveManager>
{
    SaveData _data;
    public string[] field1 = new string[2], field2 = new string[2], field3 = new string[2], field4 = new string[2];
    public List<string[]> FIELD1;

    // Use this for initialization
    protected override void Awake()
    {
        if (this != Instance)
        {
            Destroy(this);
            return;
        }

        DontDestroyOnLoad(this.gameObject);

    }

    // Use this for initialization
    void Start()
    {
        FIELD1 = new List<string[]>() { field1, field2, field3, field4 };

    }

    // Update is called once per frame
    void Update()
    {

    }

    public void GameDataSending()
    {
        //Data Initialization
        _data = new SaveData();
        _data.main();

        //Store the data you want to save from here.
        int p = 0;
        foreach (string[] g in FIELD1)
        {
            int e = 0;
            foreach (string h in g)
            {
                _data.FIELD1[p][e] = h;
                e += 1;
            }
            p += 1;
        }
        p = 0;

        SaveGame();

    }

    public void GameDateReceiving()
    {
        _data = new SaveData();
        _data = LoadFromJson(SaveData.FilePath);
        _data.main();

        FIELD1 = _data.FIELD1;
    }

    /// <summary>
    /// file writing
    /// </summary>
    /// <param name="filePath">File Location</param>
    public void SaveToJson(string filePath, SaveData data)
    {
        using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
        {
            using (StreamWriter sw = new StreamWriter(fs))
            {
                sw.WriteLine(JsonUtility.ToJson(data));
                sw.Flush();
                sw.Close();
            }
            fs.Close();
        }
    }

    /// <summary>
    /// Read the file
    /// </summary>
    /// <param name="filePath">File Location</param>
    /// <returns></returns>
    public SaveData LoadFromJson(string filePath)
    {
        if (!File.Exists(filePath))
        {//If the file does not exist = FALSE.
            Debug.Log("FileEmpty!");
            return new SaveData();//If the file does not exist, it will be new.
        }

        using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        {
            using (StreamReader sr = new StreamReader(fs))
            {
                SaveData sd = JsonUtility.FromJson<SaveData>(sr.ReadToEnd());
                if (sd == null) return new SaveData();
                return sd;
            }
        }
    }

    public void SaveGame()
    {
        SaveToJson(SaveData.FilePath, _data);
    }

    public void DeleteSave()
    {
        File.Delete(SaveData.FilePath);
    }

}

First of all, inherit from “public class SaveManager : SingletonMonoBehaviour”.

This is an object to put in your Unity game; writing to Json requires using data from a native script that does not extend MonoBehaviour.

That’s where the “SaveManager” class is used to instantiate and write the “SaveData” object.

Since it’s a singleton object, we use Awake() to DontDestroyOnLoad. By doing this, it won’t be destroyed even if it crosses over to another scene, and it will remain as a single object.

In Unity, we can only use arrays up to second order, so we stored them in the List.

in any case

public string[] field1 = new string[2], field2 = new string[2], field3 = new string[2], field4 = new string[2];
public List<string[]> FIELD1;

    void Start()
    {
        FIELD1 = new List<string[]>() { field1, field2, field3, field4 };

    }

We need to initialize the arrays; TETRY 3D has 2000 “fields” to store information on 2000 objects. Is there a way to declare them automatically…

    public void GameDataSending()
    {
        //Initialize data
        _data = new SaveData();
        _data.main();

        //Store the data you want to save from here.
        int p = 0;
        foreach (string[] g in FIELD1)
        {
            int e = 0;
            foreach (string h in g)
            {
                _data.FIELD1[p][e] = h;
                e += 1;
            }
            p += 1;
        }
        p = 0;

        SaveGame();

    }

    public void SaveGame()
    {
        SaveToJson(SaveData.FilePath, _data);
    }

To save your game, you must first transfer the data to the “SaveManager” using a separate script. For example, if you want to use

    public void SaveTest()
    {
        SaveManager.Instance.FIELD1[0][0] = "Apple";
        SaveManager.Instance.FIELD1[0][1] = "Bee";
        SaveManager.Instance.FIELD1[1][0] = "Container";
        SaveManager.Instance.FIELD1[1][1] = "Dream";
        SaveManager.Instance.FIELD1[2][0] = "Earth";
        SaveManager.Instance.FIELD1[2][1] = "Function";
        SaveManager.Instance.FIELD1[3][0] = "Gravity";
        SaveManager.Instance.FIELD1[3][1] = "Hold";
        SaveManager.Instance.GameDataSending();
    }

This is how it works. The singleton “SaveManager” can be accessed from any script with “SaveManager.Instance.

After entering the data, call GameDataSending().

In GameDataSending(), the received data is stored in SaveData.

Finally, the last one called by “SaveGame()” is

    /// <summary>
    /// File writing
    /// </summary>
    /// <param name="filePath">File Location</param>
    public void SaveToJson(string filePath, SaveData data)
    {
        using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
        {
            using (StreamWriter sw = new StreamWriter(fs))
            {
                sw.WriteLine(JsonUtility.ToJson(data));
                sw.Flush();
                sw.Close();
            }
            fs.Close();
        }
    }

SaveToJson(string filePath, SaveData data).

Write and save to a Json file created using FileStream.

The loading procedure can be reversed so that

    public void GameDateReceiving()
    {
        _data = new SaveData();
        _data = LoadFromJson(SaveData.FilePath);
        _data.main();

        FIELD1 = _data.FIELD1;
    }

    /// <summary>
    /// Read the file
    /// </summary>
    /// <param name="filePath">File Location</param>
    /// <returns></returns>
    public SaveData LoadFromJson(string filePath)
    {
        if (!File.Exists(filePath))
        {//If the file does not exist = FALSE.
            Debug.Log("FileEmpty!");
            return new SaveData();//If the file does not exist, it will be new.
        }

        using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        {
            using (StreamReader sr = new StreamReader(fs))
            {
                SaveData sd = JsonUtility.FromJson<SaveData>(sr.ReadToEnd());
                if (sd == null) return new SaveData();
                return sd;
            }
        }
    }

After new SaveData, use LoadFromJson(string filePath) to store the Json file in SaveData. Then you can instantiate the List and paste it into the List in SaveManager to store it in the Unity object.

Let’s take a look at “SaveData”.

SaveData

using System.Collections.Generic;
using UnityEngine;

[SerializeField]
public class SaveData
{

    private static string filePath = Application.persistentDataPath + "/savedata.json";//セーブデータのファイルパス
    public static string FilePath
    {//file path properties
        get { return filePath; }
    }

    //Here we have a variable with the same structure as SaveManager.
    public string[] field1 = new string[2], field2 = new string[2], field3 = new string[2], field4 = new string[2];
    public List<string[]> FIELD1;


    public void main()
    {
        //Initialization of List, etc.
        FIELD1 = new List<string[]>() { field1, field2, field3, field4 };
    }

}

First, we store the location of the file in FilePath. In this case, we’re using “persistentDataPath”. The file name is your choice.

The structure of the data should be exactly the same as in SaveManager.

Also, I think it’s a good idea to put the necessary processing in Main() and run it before moving the data.

SingletonMonoBehaviour

using UnityEngine;

public class SingletonMonoBehaviour<T> : MonoBehaviourWithInit where T : MonoBehaviourWithInit
{
    private static T instance;
    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                instance = (T)FindObjectOfType(typeof(T));

                if (instance == null)
                {
                    Debug.LogError(typeof(T) + "is nothing");
                }
            }
            else
            {
                instance.InitIfNeeded();
            }
            return instance;
        }
    }

}

public class MonoBehaviourWithInit : MonoBehaviour
{

    //A flag as to whether it was initialized or not (so that the initialization only runs once)
    private bool _isInitialized = false;

    /// <summary>
    /// We'll initialize it if we have to.
    /// </summary>
    public void InitIfNeeded()
    {
        if (_isInitialized)
        {
            return;
        }
        Init();
        _isInitialized = true;
    }

    /// <summary>
    /// Initialization (only done once, either at Awake or the first access before that)
    /// </summary>
    protected virtual void Init() { }

    //Created in virtual for sealed override
    protected virtual void Awake() { }

}

Awake() is not checked. Please see the link for details.

Usage

To use it, first paste “SaveManager” into an empty Game Object.

Then we use the game’s script to store the data we want to save in the variables we have prepared in the “SaveManager”.

Once everything is stored, call “SaveManager.Instance.

GameDateReceiving()” is executed when loading, then the data in the “SaveManager” is stored in the game’s script after executing “SaveManager.

Since the data is in a list, you can use a “for” statement to extract the data one by one.

Summary

In this article, we have shown you how to save and load large and complex data structures.

In this case, I did not encrypt or hash the data for the sake of speed up the process, but you may be able to do so if you do some things in GameDataSending() before GameSave(). I haven’t tried this before.

Since this is Json, you can save various structures, not only List and Jag array, but also other structures, but you need to confirm that you write in string form.

This concludes the techniques used in the 3rd TETRY 3D.

So thank you for reading carefully.

It was GE Planet.



0 Comments

Make a comment