Search Unity

Question Save/Load Scriptable Object Map Data

Discussion in 'Scripting' started by Courier_Games, Mar 30, 2023.

  1. Courier_Games

    Courier_Games

    Joined:
    May 14, 2022
    Posts:
    16
    Hey everybody,

    I'm basically creating some basic voxel-style map editor for myself. However I want to include a save/load System with it.

    Right now I simply store the data in a three dimensional array of ScriptableObjects of VoxelType which right now has no properties on its own but is inherited by multiple other objects.

    Code (CSharp):
    1. public class VoxelType : ScriptableObject
    2. {
    3.  
    4. }
    5.  
    The Voxeltype is inherited by for example BlockType. The idea here being that the map can store things like blocks but also specific objects that just get spawned and not integrated into the voxel mesh.

    Code (CSharp):
    1. public class BlockType : VoxelType
    2. {
    3.     [SerializeField]
    4.     private bool _isOpaque;
    5.     public bool IsOpaque { get { return _isOpaque; }}
    6.  
    7.     [SerializeField]
    8.     private Texture2DArray _textureArray;
    9.     public Texture2DArray TextureArray { get { return _textureArray; } }
    10.  
    11. ....

    Code (CSharp):
    1. public VoxelType[,,] voxels;
    2.  
    Here however I was wondering how to best create a save/load system for this kind of data.

    I think I won't be able to simply serialize the array itself, as it is just filled with references to scriptable objects.

    I was considering to store the ID or the name of the specific ScriptableObject(e.g. "GrassBlock1) during saving and using either a "VoxelDatabase" ScriptableObject to store the references or use Adressables (if that would be a good use-case).

    Also i wonder how to store serialize the data as the way my mapdata works right now is polymorphic.

    BlockType derived from VoxelType will most likely have different fields then for example
    ObjectType (like a door or a trap) also derived from VoxelType would have... Not too mention something like "directionalObjecttype" that could fit multiple objects into the same block-space (think signs like in minecraft in which you can have multiple signs in the same blockspace.

    Apart from all this regards saving/loading I was wondering if using Scriptable Objects for my Voxeltypes might be a slower solution compared to just using indicies and a static lookup object.
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    This is how we do it but making a database of objects seems silly: they're already IN the filesystem and the filesystem IS a database. Not only that but a single folder of the filesystem actually also guarantees unique naming.

    Filesystems for the win-win-win... plus... strings are SUPER easy to debug.

    Using Addressables also seems like massive hassle overkill, at least for such a simple fundamental plumbing function.

    Instead we just use
    Resources.Load<T>();
    and a custom
    Newtonsoft.Json.JsonConverter
    that serializes the data as a string, and deserializes it by looking it up with Resources.Load

    Works a treat! Be sure to have a fallback plan for failing to locate the resources.

    Beyond that...

    Load/Save steps:

    https://forum.unity.com/threads/save-system-questions.930366/#post-6087384

    An excellent discussion of loading/saving in Unity3D by Xarbrough:

    https://forum.unity.com/threads/save-system.1232301/#post-7872586

    Loading/Saving ScriptableObjects by a proxy identifier such as name:

    https://forum.unity.com/threads/use...lds-in-editor-and-build.1327059/#post-8394573

    When loading, you can never re-create a MonoBehaviour or ScriptableObject instance directly from JSON. The reason is they are hybrid C# and native engine objects, and when the JSON package calls
    new
    to make one, it cannot make the native engine portion of the object.

    Instead you must first create the MonoBehaviour using AddComponent<T>() on a GameObject instance, or use ScriptableObject.CreateInstance<T>() to make your SO, then use the appropriate JSON "populate object" call to fill in its public fields.

    If you want to use PlayerPrefs to save your game, it's always better to use a JSON-based wrapper such as this one I forked from a fellow named Brett M Johnson on github:

    https://gist.github.com/kurtdekker/7db0500da01c3eb2a7ac8040198ce7f6

    Do not use the binary formatter/serializer: it is insecure, it cannot be made secure, and it makes debugging very difficult, plus it actually will NOT prevent people from modifying your save data on their computers.

    https://docs.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide
     
    Last edited: Mar 30, 2023
    Courier_Games likes this.
  3. Courier_Games

    Courier_Games

    Joined:
    May 14, 2022
    Posts:
    16
    Thank you for the thorough answer! You are right, if I got the file-names anyway I won't need a database.

    In regards to my other question for saving polymorphic data I found a Video and one of the comments mentioned that if you want to save a field as a reference not a value using [SerializeReference] would enable unity to simply write all the necessary data to a Jones like it would do for value fields. Is that correct?

    Edit:
    However I don't really need to serialize the whole scriptable object, as they stay the same anyway. However I might at a later point have to wrap my scriptable objects in another class to store some state variables in the grid as well, but that is not relaxed to save/load at all.
     
    Last edited: Mar 31, 2023
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    I have not had occasion to fiddle with SerializeReference yet so I cannot comment.

    You might want to construct a new post clearly asking your question, because I think many good people here like Bunny and Spiney and RadRed might all know a lot about the details of SerializeReference
     
    Courier_Games likes this.