Saving ScriptableObject fields from a Custom Editor and using UIElements

I’ve been more familiar with IMGUI (though I’m still new to the whole Unity things) but I’ve read that when you’re working on a custom editor for a ScriptableObject, you have to manually save properties using the EditorUtility.SetDirty() as well as doing AssetDatabase.SaveAssets().

Since you’re using a different method to draw your UIElements, how do I save properties with a custom editor for ScriptableObjects?

Here’s the code that I’m trying to apply this feature for:

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

[CreateAssetMenu(menuName = "Map")]
public class Map : ScriptableObject
{
    [SerializeField]
    private int[] mapping;

    void Start() {
        mapping = new int[361];
    }

    public string GetMap() {
        string res = "";
        foreach(int entry in mapping) {
            res += entry.ToString();
        }
        return res;
    }
}
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor;
using UnityEditor.UIElements;

[CustomEditor(typeof(Map))]
public class MapEditor : Editor
{
    SerializedProperty mapping;

    VisualElement root;
    VisualTreeAsset tree;

    public void OnEnable() {
        root = new VisualElement();

        tree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
                "Assets/Scripts/Editor/MapEditor.uxml");

        var stylesheet =
            AssetDatabase.LoadAssetAtPath<StyleSheet>(
                "Assets/Scripts/Editor/MapEditor.uss");
        root.styleSheets.Add(stylesheet);

        mapping = serializedObject.FindProperty("mapping");
        Debug.Log(mapping.arraySize);
        //VisualElement labelFromUXML = tree.CloneTree();
        //root.Add(labelFromUXML);
    }

    public override VisualElement CreateInspectorGUI() {
        VisualElement newRoot = this.root;
        //Debug.Log(newRoot);
        newRoot.Clear();

        tree.CloneTree(newRoot);

        Box treeBox = newRoot.Query<Box>();

        Debug.Log(treeBox);

       
        for(int x = 0; x<19; ++x) {
            VisualElement rowBox = new VisualElement();
            rowBox.ToggleInClassList("row-container");
            for(int y = 0; y<19; ++y) {
                Button newButton = new Button();
                newButton.ToggleInClassList("map-piece-space");
                newButton.name = x + "," + y;
                newButton.clickable.clicked += () => ChangeEntryType(newButton);
                rowBox.Add(newButton);
            }
            treeBox.Add(rowBox);
        }

        return newRoot;
    }

    private void ChangeEntryType(Button btn) {
        int x = int.Parse(btn.name.Split(',')[1]);
        int y = int.Parse(btn.name.Split(',')[0]);
        List<string> theTypes = new List<string> {"map-piece-space", "map-piece-space2", "map-piece-wall", "map-piece-wall2"};
        string myType = "";
        List<string> classes =  btn.GetClasses().ToList();

        foreach(string theType in theTypes) {
            if (classes.Contains(theType)) {
                myType = theType;
                break;
            }
        }

        btn.ToggleInClassList(myType);
        int index = (19*y) + x;
        Debug.Log(index);
        Debug.Log(mapping.arraySize);

        switch(myType) {
            case "map-piece-space": {
                myType = "map-piece-space2";
                mapping.GetArrayElementAtIndex(index).intValue = 1;
            }  break;
            case "map-piece-space2": {
                myType = "map-piece-wall";
                mapping.GetArrayElementAtIndex(index).intValue = 2;
            }  break;
            case "map-piece-wall": {
                myType = "map-piece-wall2";
                mapping.GetArrayElementAtIndex(index).intValue = 3;
            } break;
            case "map-piece-wall2": {
                myType = "map-piece-space";
                mapping.GetArrayElementAtIndex(index).intValue = 0;
            }  break;
        }

        btn.ToggleInClassList(myType);

        //EditorUtility.SetDirty(mapping);
        //AssetDatabase.SaveAssets();

    }
}

Since you’re drawing the entire array UI manually and not using something like a PropertyField element (equivalent to IMGUI PropertyField), you should be able to just uncomment your to lines to save the asset and probably add a:

serializedObject.ApplyModifiedProperties()

and it should work.

If you do decide to rely on the automatic binding and UI creation of PropertyField, you can use events registered at the root of your UI (your root element) where you detect any ChangeEvents of interest and save the asset then.