Scriptable Object not Saving Data

Hello everyone,

I’m having some trouble with my Scriptable Object in Unity. I’m trying to use it to store data for my game, whenever I enter play mode or restart Unity, the data is lost.

I am trying to create a Scriptable Object that allows me to easily specify the boundary rules for a procedural generation project. Here are my two scripts: the main script and the editor.

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;

[CreateAssetMenu(fileName = "New Prototype", menuName = "Prototype")]
public class Prototype : ScriptableObject
{
    public Tile tile;

    private Dictionary<int, bool[]> connections = new Dictionary<int, bool[]>();

    public bool[] GetConnections(int id)
    {
        if (!connections.ContainsKey(id))
        {
            connections[id] = new bool[4];
        }

        return connections[id];
    }
}
using System.Linq;
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(Prototype))]
public class PrototypeEditor : Editor
{
    private Prototype prototype;

    private void OnEnable()
    {
        prototype = (Prototype) target;
    }

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        Prototype[] prototypes = AssetDatabase.FindAssets("t:Prototype")
            .Select(guid => AssetDatabase.LoadAssetAtPath<Prototype>(AssetDatabase.GUIDToAssetPath(guid)))
            .ToArray();

        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.LabelField("", GUILayout.Width(EditorGUIUtility.labelWidth - 4f));

        EditorGUILayout.LabelField("Right", GUILayout.Width(40f));
        EditorGUILayout.LabelField("Left", GUILayout.Width(40f));
        EditorGUILayout.LabelField("Up", GUILayout.Width(40f));
        EditorGUILayout.LabelField("Down", GUILayout.Width(40f));

        EditorGUILayout.EndHorizontal();

        foreach (var p in prototypes)
        {
            EditorGUILayout.BeginHorizontal();

            EditorGUILayout.LabelField(p.name, GUILayout.Width(EditorGUIUtility.labelWidth - 4f));

            bool[] connections = prototype.GetConnections(p.GetInstanceID());

            connections[0] = EditorGUILayout.Toggle(connections[0], GUILayout.Width(40f));
            connections[1] = EditorGUILayout.Toggle(connections[1], GUILayout.Width(40f));
            connections[2] = EditorGUILayout.Toggle(connections[2], GUILayout.Width(40f));
            connections[3] = EditorGUILayout.Toggle(connections[3], GUILayout.Width(40f));

            EditorGUILayout.EndHorizontal();
        }
    }
   
    public override Texture2D RenderStaticPreview(string assetPath, Object[] subAssets, int width, int height)
    {
        Prototype scriptableObject = target as Prototype;
        if (scriptableObject == null) return null;

        Texture2D icon = null;
        if (scriptableObject.tile != null)
        {
            icon = AssetPreview.GetAssetPreview(scriptableObject.tile);
        }

        if (icon == null)
        {
            icon = base.RenderStaticPreview(assetPath, subAssets, width, height);
        }

        return icon;
    }
}

This results in the desired setup seen below.

8937417--1225683--upload_2023-4-9_20-22-38.png

I am able to check the boxes and see the updates, but the data is lost if I enter play mode or restart unity.

I’ve tried calling AssetDatabase.SaveAssets(), SetDirty(), and trying various methods to serialize the data, but nothing is working.

Does anyone know how I can fix this issue? Any help would be greatly appreciated.

Thank you in advance!

I have looked at quite a few forum posts suggesting this. I have been trying to implement this for hours to no avail. Can you be more specific?

    public void SetConnections(int id, bool[] _connections)
    {
        connections[id] = _connections;

        EditorUtility.SetDirty(this)
    }

Is this correct?

        foreach (var p in prototypes)
        {
            EditorGUILayout.BeginHorizontal();

            EditorGUILayout.LabelField(p.name, GUILayout.Width(EditorGUIUtility.labelWidth - 4f));

            bool[] connections = prototype.GetConnections(p.GetInstanceID());

            connections[0] = EditorGUILayout.Toggle(connections[0], GUILayout.Width(40f));
            connections[1] = EditorGUILayout.Toggle(connections[1], GUILayout.Width(40f));
            connections[2] = EditorGUILayout.Toggle(connections[2], GUILayout.Width(40f));
            connections[3] = EditorGUILayout.Toggle(connections[3], GUILayout.Width(40f));

            prototype.SetConnections(p.GetInstanceID(), connections);
         
            EditorGUILayout.EndHorizontal();
        }

There are a few reasons you are not able to save your data. Unity’s serializer has a number of rules that it follows. You can find more details in this link to the manual about the serializer.

The most notable things to consider are:

  1. The data must be public or have the [SerializeField] attribute applied to it. Your data in this case has neither.
  2. If it’s a struct or class, that type must be marked with the [Serializable] attribute.
  3. It must be a type that Unity is capable of serializing. The list can be found in the link I provided above. This is also holding you back as you are using a Dictionary which cannot be serialized.
  4. Must be a field and not a property.

Dictionaries cannot be serialized by Unity natively so you have to consider a few options. Tools like Odin or FullSerializer can serialize them but generally it’s still not always recommended for a variety of technical reasons mostly to do with what people sometimes try to use as keys. In your case they’d be fine. In both cases however you end up having to derive from one of their base classes which sorta locks you into using them forever for that piece of code. You could search for a serializable Dictionary that is compatible with Unity somewhere online. Again, not always the safest option but definitely doable in your case due to what you are storing and probably the easiest way to go. For a more DIY approach you can implement the interface ISerializationCallbackReceiver. Typically in this case you’d keep arrays of the keys and values that are private and marked with [SerializeField]. During serialization you’d push your dictionary to them. During deserialization you’d pull from those arrays to reconstruct your dictionary.

4 Likes