Unity Scriptable Object does not retain his values after the editor is closed

4.3k views Asked by At

I have the following scriptable object:

[CreateAssetMenu(fileName = "Assets/Gladio Games/Resources/Levels/LEVLE_TMP", menuName = "Utils/Crossword/Generate Empty Level", order = 100)]
public class Level : ScriptableObject
{
    public char[,] Table { get; protected set; }
    public  List<Crossword> Crosswords { get; protected set; }
    protected static char EMPTY_CHAR = '\0';


    public Crossword GetCrosswordAtIndex(int x, int y, Direction direction)
    {
        //Ottieni la prima e l'unica parola che si trova sotto quell'indice
        return Crosswords.First(crossword => crossword.Direction == direction && crossword.GetCrosswordIndexes().Contains(new Vector2(x, y)));
    }
}

This is the code that I use to save the Scriptable Object

private static void Save(Level level, string path)
{            
    EditorUtility.SetDirty(level);
    AssetDatabase.CreateAsset(level, path + ".asset");
    AssetDatabase.SaveAssets();
    AssetDatabase.Refresh();
        EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
}

The object is created successfully through an editor script and it has all the data saved, but if I close and reopen the Editor, and try to load the fields are null. The script to save and load the scriptable object are used within the scene

What I'm doing wrong?

1

There are 1 answers

0
derHugo On BEST ANSWER

Checkout the Script serialization rules

In particular in your case

  • Serializers in Unity work directly on the fields of your C# classes rather than their properties, so there are rules that your fields must conform to to be serialized.

  • Note: Unity doesn’t support serialization of multilevel types (multidimensional arrays, jagged arrays, dictionaries, and nested container types)

So both your properties are not serialized (= saved) at all.


As a solution

  • For Crosswords

    • Make sure that the type Crossword itself is [Serializable]

    • Use a field instead

      public  List<Crossword> Crosswords;
      
  • For Table there are multiple ways.

    I would probably simply use a wrapper class like e.g.

     // Could of course make this generic but for the drawer it easier this way for now
     [Serializable]
     public class Table
     {
         [SerializeField]
         [Min(1)]
         private int xDimension = 1;
    
         [SerializeField]
         [Min(1)]
         private int yDimension = 1;
    
         [SerializeField]
         private char[] flatArray = new char[1];
    
         public Table() { }
    
         public Table(char[,] array)
         {
             xDimension = array.GetLength(0);
             yDimension = array.GetLength(1);
             flatArray = new char[xDimension * yDimension];
    
             for (var x = 0; x < xDimension; x++)
             {
                 for (var y = 0; y < yDimension; y++)
                 {
                     flatArray[x * yDimension + y] = array[x, y];
                 }
             }
         }
    
         public Table(int x, int y)
         {
             xDimension = x;
             yDimension = y;
             flatArray = new char[x * y];
         }
    
         public Table(char[] array, int x, int y)
         {
             xDimension = x;
             yDimension = y;
             flatArray = array;
         }
    
         public char this[int x, int y]
         {
             get => flatArray[x * yDimension + y];
    
             set => flatArray[x * yDimension + y] = value;
         }
    
     #if UNITY_EDITOR
         [CustomPropertyDrawer(typeof(Table))]
         private class TableDrawer : PropertyDrawer
         {
             private const int k_elementSpacing = 5;
    
             public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
             {
                 var height = 1f;
    
                 if (property.isExpanded)
                 {
                     height += property.FindPropertyRelative(nameof(Table.xDimension)).intValue * 1.5f;
                 }
    
                 return height * EditorGUIUtility.singleLineHeight;
             }
    
             public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
             {
                 var foldoutRect = position;
                 foldoutRect.height = EditorGUIUtility.singleLineHeight;
                 foldoutRect.width = EditorGUIUtility.labelWidth;
                 property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, label);
    
                 if (property.isExpanded)
                 {
                     var xRect = position;
                     xRect.height = EditorGUIUtility.singleLineHeight;
                     xRect.x += foldoutRect.width;
                     xRect.width -= foldoutRect.width;
                     xRect.width *= 0.5f;
    
                     var yRect = xRect;
                     yRect.x += xRect.width;
    
                     var xDimension = property.FindPropertyRelative(nameof(Table.xDimension));
                     var yDimension = property.FindPropertyRelative(nameof(Table.yDimension));
                     var array = property.FindPropertyRelative(nameof(Table.flatArray));
    
                     using (var changeCheck = new EditorGUI.ChangeCheckScope())
                     {
                         EditorGUI.PropertyField(xRect, xDimension, GUIContent.none);
                         EditorGUI.PropertyField(yRect, yDimension, GUIContent.none);
    
                         if (changeCheck.changed)
                         {
                             array.arraySize = xDimension.intValue * yDimension.intValue;
                         }
                     }
    
                     position.y += EditorGUIUtility.singleLineHeight * 1.5f;
                     EditorGUI.indentLevel++;
                     var elementRect = EditorGUI.IndentedRect(position);
                     EditorGUI.indentLevel--;
    
                     var elementWidth = elementRect.width / yDimension.intValue - k_elementSpacing;
                     var elementHeight = EditorGUIUtility.singleLineHeight;
    
                     var currentPosition = elementRect;
                     currentPosition.width = elementWidth;
                     currentPosition.height = elementHeight;
    
                     for (var x = 0; x < xDimension.intValue; x++)
                     {
                         for (var y = 0; y < yDimension.intValue; y++)
                         {
                             var element = array.GetArrayElementAtIndex(x * yDimension.intValue + y);
    
                             EditorGUI.PropertyField(currentPosition, element, GUIContent.none);
    
                             currentPosition.x += elementWidth + k_elementSpacing;
                         }
    
                         currentPosition.x = elementRect.x;
                         currentPosition.y += elementHeight * 1.5f;
                     }
                 }
             }
         }
     #endif
     }
    

    And then rather have e.g.

    public Table Table;