Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Best way to save data?

Discussion in 'General Discussion' started by wannaMakeAGame, May 14, 2023.

  1. wannaMakeAGame

    wannaMakeAGame

    Joined:
    Dec 7, 2020
    Posts:
    47
    Hey, I wonder what are the best practices to save data in unity. I don't mean saving simple data like int or string, I mean complex data, lets say complex data of a lot of object?. I know about general things like binary and json, but I wanna hear about advanced practices using them. -And please provide examples if you have them.

    (Also I bought Odin Serializer but have no idea how to use it.)
     
  2. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,082
    wannaMakeAGame likes this.
  3. wannaMakeAGame

    wannaMakeAGame

    Joined:
    Dec 7, 2020
    Posts:
    47
  4. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,453
    Most likely no complete project will have exactly the structure of the game or data where you want to add such functionality to.
    Also writing complex objects (which are by the way also just nested ints, floats, strings etc.) to json is the same as if writing very simple ones.

    What data exactly do you want to write? The state of the game for a save file?
     
    wannaMakeAGame likes this.
  5. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,900
    There is no universal "deeper understanding", everything depends on your own game structure and code architecture. You take the simple example, go through your entire code and figure out how you can write your valuable simple data and then read it back to get back the very same state you left. There is no magic. The earlier you think about how to save and load data in your game the "easier" it will be. But it's just meticulously going through the data you need for your game state to reinstated and manage it.
     
    wannaMakeAGame likes this.
  6. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,082
    Like was mentioned there is no deep understanding to be had. You define the way the data is represented in your project, and when you want to load or save that data you call the appropriate APIs.
     
    wannaMakeAGame likes this.
  7. wannaMakeAGame

    wannaMakeAGame

    Joined:
    Dec 7, 2020
    Posts:
    47
    I have a sliding puzzle game, When the game is closed, I wanna save location of the every puzzle block and load it back when I open it again. My idea was to give every block an id, then create a dictionary that matches id of the block with its position, and put them back to appropriate location depending on blocks id when the game is loaded. But I don't know how to save a dictionary. I heard it is not serializable? but some code I saw online seems like saving a dictionary

    Apart from this goal, my main objective when I asked this question was to learn about good practices to save data. Since it gets chaotic very fast, whenever I give it a try.
     
  8. wannaMakeAGame

    wannaMakeAGame

    Joined:
    Dec 7, 2020
    Posts:
    47
    Also I am confused about what is serializable and non-serializable, what are the serializable things in unity(Transform?, GameObjects?, Components?) and how it refers to saving to json generally
     
  9. wannaMakeAGame

    wannaMakeAGame

    Joined:
    Dec 7, 2020
    Posts:
    47
    How?
    just [OdinSerialize] attribute?
     
  10. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,900
    https://odininspector.com/tutorials/serialize-anything/serializing-dictionaries#odin-serializer

    Please RTFM. We are not ChatGPT agents, I assure you, we use the same google / bing search as you would if you were willing to.

    And obviously if you really stuck somewhere, feel free to ask your questions. About those. Show us what have you done to achieve your goals, paste some code you tried and describe what you don't understand. Open ended questions, asking for book-worth of explanations rarely work on the forums. If you have concrete questions, we are happy to help.
     
    neginfinity likes this.
  11. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,082
    You could use a dictionary but a list would be just fine too. Code below was partially generated with ChatGPT and has not been tested but should work.
    Code (csharp):
    1. [Serializable]
    2. public struct BlockData
    3. {
    4.     public int id;
    5.     public Vector2 position;
    6. }
    Code (csharp):
    1. [Serializable]
    2. public class GameState
    3. {
    4.     public List<BlockData> blocks = new List<BlockData>();
    5. }
    Code (csharp):
    1. using System.IO;
    2. using Sirenix.Serialization;
    3.  
    4. public class GameManager
    5. {
    6.     public GameState LoadGameData(string path)
    7.     {
    8.         var data = File.ReadAllBytes(path);
    9.         return SerializationUtility.DeserializeValue<GameState>(data, DataFormat.Binary);
    10.     }
    11.  
    12.     public void SaveGameData(GameState state, string path)
    13.     {
    14.         var data = SerializationUtility.SerializeValue<GameState>(state, DataFormat.Binary);
    15.         File.WriteAllBytes(path, data);
    16.     }
    17. }
    You inherit from SerializedMonoBehaviour instead of MonoBehaviour.
    Code (csharp):
    1. public MyScript : SerializedMonoBehaviour
    2. {
    3.     public Dictionary<int, Vector2> blocks;
    4. }
     
    Last edited: May 14, 2023
    wannaMakeAGame likes this.
  12. wannaMakeAGame

    wannaMakeAGame

    Joined:
    Dec 7, 2020
    Posts:
    47
    I was on this page already after my comment, as I said I am not in a rush about my case and I try to learn about general methods, I just try to hear about other peoples methods, since there is never a single way to do something
     
  13. wannaMakeAGame

    wannaMakeAGame

    Joined:
    Dec 7, 2020
    Posts:
    47
    Thanks for the scripts! It is the exact method I thought I would use. But I am still confused about serializedmonobehaviour dictionary. After that do I just save the dictionary to a json file? Or save the class as a whole?
     
  14. PanthenEye

    PanthenEye

    Joined:
    Oct 14, 2013
    Posts:
    1,754
    SerializedMonobehaviour is for Dictionary support in the Inspector afaik. Odin Serializer should serialize dictionaries to file by default so you just use their SerializationUtility API the regular way. I don't recall doing anything special for it but it's been awhile.

    Note that Odin Serializer by default saves in binary format and their JSON implementation is not clean. It adds a bunch of serializer specific metadata and if I recall correctly, it also wasn't optimized so all the performance benchmarks don't really list Odin's JSON performance. For a puzzle game, it should be good enough though.
     
    Ryiah and wannaMakeAGame like this.
  15. wannaMakeAGame

    wannaMakeAGame

    Joined:
    Dec 7, 2020
    Posts:
    47
    how? No need to serialize again?
     
  16. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,082
    Odin is designed to be quick to use and a major part of that is it's doing all of the heavy lifting under the hood. If you want to know how it does it you'd have to look at the source code.
     
    Last edited: May 15, 2023
  17. wannaMakeAGame

    wannaMakeAGame

    Joined:
    Dec 7, 2020
    Posts:
    47
    Also another thing confusing me is, on internet it says gameobjects and components are serializable in unity, but when I serialize them to json, they don’t have any of the fields saved to the file, lets say I have rigidbody attached to the gameobject and this rigidbody has mass property equal to 1. I feel like I should be able to serialize this value too when I serialize a gameobject, but it doesn’t work that way. so what is the point of serializing a gameobject or a component if doesn’t serialize any of the components values or a simply not even the transform values?
     
  18. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,082
    Without seeing the classes my guess would be you haven't set them up in a way that it expects.

    https://docs.unity3d.com/Manual/script-Serialization.html
     
  19. wannaMakeAGame

    wannaMakeAGame

    Joined:
    Dec 7, 2020
    Posts:
    47
  20. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,082
  21. wannaMakeAGame

    wannaMakeAGame

    Joined:
    Dec 7, 2020
    Posts:
    47
    So I assume you need to save and load every value you need by hand? For example if you want to save the box collider component, you cant just save the box collider, you need to save size and center of the boxcollider by hand seperately
     
  22. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,082
    No. You just need to use data types that are serializable. You can't save a GameObject.
     
  23. wannaMakeAGame

    wannaMakeAGame

    Joined:
    Dec 7, 2020
    Posts:
    47
    But sometimes when you serialize a field individually it works, but it doesn’t when it is in a class. I wonder what it means when they say gameobject and components are serializable, and what is the use case
     
  24. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,082
    I'd have to see an example to provide you with more information.
     
  25. wannaMakeAGame

    wannaMakeAGame

    Joined:
    Dec 7, 2020
    Posts:
    47
    What are they? Int, float, string I know. You say data types, but classes also become serializable when serializable attrbt added. So confusing
    I am not on computer now, but today I tried to serialize a dictionary with odin and it worked, but then I tried to serialize the serializedmonobehaviour completely, I saw a dictionary in the json file, but pairs in the dictionary was not there.
     
  26. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    As you may have learnt or will learn you cannot serialise out any Unity objects*, or references to Unity objects (nor can you attempt to deserialise them). You generally need to manually manage the conversion by having some kind of serialised surrogate class.

    Really there are two main ways:
    • Convert the Unity data into a serialisable form and vice versa
    • Keep all pertinent data in non-Unity object form so it can just be serialised out without conversion
    And probably more that don't immediately come to mind. Either approach works best in different kinds of projects.

    And there are different ways to save data than just wrapping it up into a class and serialising it out. You might use XML, or NBT (Named Binary Tag) depending on the needs of your project.

    Before that you need to know what you actually care about saving, and only to save as much as necessary. Everyone seems to want to save absolutely everything going on, when you often only need one tenth of that information, if not less. You also need to design your systems with saving in mind. It often leads to ton of refactoring if you leave it to after the fact.

    So in short, even the Odin serialiser doesn't let you magically serialise out game objects and components. You are still required to manage some kind of data structure yourself.

    * - Technically you can serialise/deserialise components and scriptable objects with
    UnityEngine.JsonUtility
    . But it's limited by Unity's very basic serialisation, and cannot serialise out references to other assets.
     
    Last edited: May 15, 2023
    wannaMakeAGame and Ryiah like this.
  27. Voronoi

    Voronoi

    Joined:
    Jul 2, 2012
    Posts:
    571
    It sounds like you want to basically 'save a scene' along with all of its gameobjects and all of their positions, like the Editor does. If you think about, this would result in a pretty large file for your players device and essentially recreates the editor.

    What I think everyone is getting at is that you shouldn't have to 'save an entire scene' to save the state of your puzzle game. You need to figure out what's important and worth saving, which typically should be ints, strings, etc. For the complex object, use a Prefab that has settings for whatever you need to save. For a puzzle game, I imagine you have a grid and a certain number of pieces in places. Save the grid location of current pieces and their important information. On restarting the game, the game should instantiate a prefab at all those locations and set up the pertinent data.
     
  28. Luemus

    Luemus

    Joined:
    May 20, 2013
    Posts:
    101
    Do yourself a favor and grab Easy Save while it's 50% off.
     
    Ryiah likes this.
  29. PanthenEye

    PanthenEye

    Joined:
    Oct 14, 2013
    Posts:
    1,754
    Easy Save can be nice but seems like an overkill for a simple puzzle game.
     
  30. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,082
  31. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Why you should not use the binary serialiser.

    It's old and out of date. There are far more modern and better serialisers to use these days.

    Edit: Oh you're just another spam bot.
     
  32. Przemyslaw_Zaworski

    Przemyslaw_Zaworski

    Joined:
    Jun 9, 2017
    Posts:
    314
    1. C# JSON serialization and deserialization (JsonUtility):

    Code (CSharp):
    1. public class SettingsData : ScriptableObject
    2. {
    3.     public int QualityLevel = -1;
    4.     public bool QualityVerticalSync = true;
    5.     public bool QualityAF = true;
    6.     public string QualityShadowQuality = "3";
    7.     public string QualityTextureQuality = "3";
    8.     public string QualityLOD = "4";
    9.     public string QualityShadowDistance = "5";
    10.     public string QualityShadowResolution = "3";
    11. }
    12.  
    13. public static SettingsData LoadSettings()
    14. {
    15.     string path = System.IO.Path.Combine(Application.streamingAssetsPath, "Settings.cfg");
    16.     SettingsData scriptableObject = ScriptableObject.CreateInstance(typeof(SettingsData)) as SettingsData;
    17.     if (File.Exists(path) == false) return scriptableObject;
    18.     StreamReader streamReader = new StreamReader(path);
    19.     string json = streamReader.ReadToEnd();
    20.     streamReader.Close();
    21.     JsonUtility.FromJsonOverwrite(json, scriptableObject);
    22.     return scriptableObject;
    23. }
    24.  
    25. public static void SaveSettings(SettingsData settingsData)
    26. {
    27.     string path = System.IO.Path.Combine(Application.streamingAssetsPath, "Settings.cfg");
    28.     string json = JsonUtility.ToJson(settingsData, true);
    29.     StreamWriter streamWriter = new StreamWriter(path);
    30.     streamWriter.Write(json);
    31.     streamWriter.Close();
    32. }
    2. BinaryFormatter:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Runtime.Serialization.Formatters.Binary;
    3. using System.IO;
    4.  
    5. public class Serialization : MonoBehaviour
    6. {
    7.     [System.Serializable]
    8.     struct Map
    9.     {
    10.         public float[,] heightmap;
    11.         public float[,] alphamap;
    12.     };
    13.  
    14.     void PrintArray (float[,] table)
    15.     {
    16.         for (int x=0;x<table.GetLength(0);x++)
    17.         {
    18.             for (int y=0;y<table.GetLength(1);y++)
    19.             {
    20.                 Debug.Log(table[x,y]);
    21.             }
    22.         }
    23.     }
    24.  
    25.     void Serialize (string path, Map source)
    26.     {
    27.         try
    28.         {
    29.             BinaryFormatter bin = new BinaryFormatter();
    30.             FileStream writer = new FileStream(path,FileMode.Create);
    31.             bin.Serialize(writer, (object)source);
    32.             writer.Close();
    33.         }
    34.         catch (IOException) {}
    35.     }
    36.  
    37.     Map Deserialize (string path)
    38.     {
    39.         FileStream reader = new FileStream(path, FileMode.Open, FileAccess.Read);
    40.         BinaryFormatter bin = new BinaryFormatter();
    41.         Map target = (Map) bin.Deserialize(reader);  
    42.         reader.Close();
    43.         return target;
    44.     }
    45.  
    46.     void Start()
    47.     {
    48.         string Path = Application.dataPath + "/StreamingAssets/" + "map.data";
    49.         Map source;
    50.         source.heightmap = new float[2,2] {{1.0f,2.0f},{3.0f,4.0f}};
    51.         source.alphamap  = new float[2,2] {{5.0f,6.0f},{7.0f,8.0f}};
    52.         Serialize (Path,source);
    53.         Map destination = Deserialize (Path);
    54.         PrintArray(destination.heightmap);
    55.         PrintArray(destination.alphamap);
    56.     }
    57. }
    3. Copy object data directly from memory, with methods from System.Runtime.InteropServices:

    Code (CSharp):
    1. // Example how to read/write objects from/to file, without serialization/deserialization.
    2. // Make sure you have created StreamingAssets directory before.
    3.  
    4. using System.Collections;
    5. using System.Collections.Generic;
    6. using UnityEngine;
    7. using System;
    8. using System.Runtime.InteropServices;
    9. using System.IO;
    10.  
    11. [StructLayout(LayoutKind.Sequential, Pack = 1)]  //read more: http://www.pzielinski.com/?p=1337
    12. public class Tree
    13. {
    14.     [MarshalAs(UnmanagedType.LPStr, SizeConst = 1)]  //allocate memory for one element of type string
    15.     public string Title;
    16.     [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]  //allocate memory for five elements of type integer
    17.     public int[] Values;
    18. }
    19.  
    20. public class ObjectManager : MonoBehaviour
    21. {
    22.     public enum State {Write, Read}  
    23.     public State Mode = State.Write;
    24.  
    25.     void SaveObjectToFile (System.Object structure, string path)
    26.     {
    27.         int size = Marshal.SizeOf(structure);
    28.         byte[] bytes = new byte[size];
    29.         IntPtr ptr = Marshal.AllocHGlobal(size);
    30.         Marshal.StructureToPtr(structure, ptr, false);
    31.         Marshal.Copy(ptr, bytes, 0, size);
    32.         Marshal.FreeHGlobal(ptr);
    33.         File.WriteAllBytes (path, bytes);
    34.     }
    35.  
    36.     T LoadObjectFromFile<T> (string path)
    37.     {
    38.         byte[] bytes = File.ReadAllBytes (path);
    39.         int size = bytes.Length;
    40.         IntPtr ptr = Marshal.AllocHGlobal(size);
    41.         Marshal.Copy(bytes, 0, ptr, size);
    42.         T structure = (T)Marshal.PtrToStructure(ptr, typeof(T));
    43.         Marshal.FreeHGlobal(ptr);
    44.         return structure;
    45.     }
    46.  
    47.     void Start()
    48.     {
    49.         if (Mode == State.Write)
    50.         {
    51.             Tree tree = new Tree();
    52.             tree.Title = "Apple";
    53.             tree.Values = new int[5] {8, 16, 33, 47, 99};
    54.             SaveObjectToFile (tree, Path.Combine(Application.streamingAssetsPath, "test.bin"));
    55.         }
    56.  
    57.         if (Mode == State.Read)
    58.         {
    59.             Tree tree = LoadObjectFromFile<Tree> (Path.Combine(Application.streamingAssetsPath, "test.bin"));
    60.             Debug.Log(tree.Title);
    61.             for (int i = 0; i < tree.Values.Length; i++) Debug.Log(tree.Values[i]);
    62.         }
    63.     }
    64. }
    Additionally, we can use FileShare.ReadWrite flag, to avoid IOException: Sharing violation:

    Code (CSharp):
    1.         byte[] GetBytesFromFilePath(string pathToOutputLog) // to avoid IOException: Sharing violation
    2.         {
    3.             using (var fileStream = File.Open(pathToOutputLog, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
    4.             {
    5.                 using (var memoryStream = new MemoryStream())
    6.                 {
    7.                     fileStream.CopyTo(memoryStream);
    8.                     return memoryStream.ToArray();
    9.                 }
    10.             }
    11.         }
     
  33. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,082
  34. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,503
    Exactly.

    To provide an example, I'm currently working on an open-world-ish game with many dozens of scenes in it. It would be utterly impractical if our saved game system stored everything from those scenes to every saved game. It would also be crazily inefficient because, if you think about it for a moment, it's pretty obvious that we don't want most of the data to change, ever. If we've got a pushable block in a level then all but one or two properties should be left exactly how they are in the Editor at build time. I don't want my saved game to store its Rigidbody settings and so on and so forth. In fact, the only thing I want to save is where it is - position and rotation.

    Next time that block is loaded, everything starts with the default settings. It then checks to see if there is any saved data for that block and, if there is, updates only the relevant properties. In this case it'll move the block to where the player put it... and everything else stays as it was. The save game file only has to hold an ID, a position, and a rotation.

    Ideally, you want to store the minimum amount of data required to re-create the game's state at any point. Your serialization system can't automatically figure out what properties are important to re-create your game state, so that's why you typically need to tell it which classes / objects and which fields to save.

    Of course, for a simple puzzle game it may be 100% feasible to use a simpler system which just stores and restores every exposed property.
     
    Ryiah and Lurking-Ninja like this.
  35. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,900
    If anyone wants to dabble in binary saving but need some written support for it Catlike Coding made an excellent tutorial in part of his hexagon series. It does worth the read if you don't know what are binary files or how to handle saving and loading using them. Obviously you will need to ignore the hex-map parts and concentrate on the loading and saving.

    https://catlikecoding.com/unity/tutorials/hex-map/part-12/
     
  36. PanthenEye

    PanthenEye

    Joined:
    Oct 14, 2013
    Posts:
    1,754
    System BinaryFormatter is deprecated.

    The new Unity Serialization package in 2022LTS is far superior and handles both JSON and Binary formats.
     
    DragonCoder likes this.