How do I add an undo functionality to my custom editor?

Hello,

I’ve been trying to add an undo functionality to my custom editor script, but in its current state if I change anything in the editor it’s not registering in unity’s undo/redo function. What do I have to change to make it work?

using UnityEngine;
using UnityEditor;


[CustomEditor(typeof(SpaceObject))]
public class SpaceObjectEditor : Editor
{

    public override void OnInspectorGUI()
    {
        SpaceObject spaceObject = (SpaceObject)target;

        base.OnInspectorGUI(); //Default Kram

        EditorGUI.BeginChangeCheck();

        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.PrefixLabel("Position of the Space-Object");
        EditorGUIUtility.labelWidth = 10f;
        
        double newX = EditorGUILayout.DoubleField("x: ", spaceObject.origin.x, GUILayout.MaxWidth(100f));
        double newY = EditorGUILayout.DoubleField("y: ", spaceObject.origin.y, GUILayout.MaxWidth(100f));
        double newZ = EditorGUILayout.DoubleField("z: ", spaceObject.origin.z, GUILayout.MaxWidth(100f));
        EditorGUILayout.EndHorizontal();

        if (EditorGUI.EndChangeCheck())
        {
            Undo.RecordObject(spaceObject, "Changed Position in Space");
            spaceObject.origin.x = newX;
            spaceObject.origin.y = newY;
            spaceObject.origin.z = newZ;
        }
    }
}

Well I’m sure the issue is that you are mixing the new serializedobject stuff (by using base.OnInspectorGUI(); ) with the old way using the target property. This most likely will interfer with your undo code afterwards. If you want to implement a proper editor you want to use the SerializedObject / SerializedProperty approach. Though if the type of your origin field is a custom struct, it would make more sense to implement a PropertyDrawer for your type (which also uses the SerializedProperty class). Your approach would display your custom struct twice unless you explicitly use [SerializeField, HideInInspector]

If you really want to roll your own custom inspector for your whole SpaceObject class, use an inspector like this:

[CustomEditor(typeof(SpaceObject))]
public class SpaceObjectEditor : Editor
{
    SerializedProperty xProperty;
    SerializedProperty yProperty;
    SerializedProperty zProperty;

    private void OnEnable()
    {
        xProperty = serializedObject.FindProperty("origin.x");
        yProperty = serializedObject.FindProperty("origin.y");
        zProperty = serializedObject.FindProperty("origin.z");
    }

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

        EditorGUI.BeginChangeCheck();

        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.PrefixLabel("Position of the Space-Object");
        EditorGUIUtility.labelWidth = 10f;

        xProperty.doubleValue = EditorGUILayout.DoubleField("x: ", xProperty.doubleValue, GUILayout.MaxWidth(100f));
        yProperty.doubleValue = EditorGUILayout.DoubleField("y: ", yProperty.doubleValue, GUILayout.MaxWidth(100f));
        zProperty.doubleValue = EditorGUILayout.DoubleField("z: ", zProperty.doubleValue, GUILayout.MaxWidth(100f));
        EditorGUILayout.EndHorizontal();

        if (EditorGUI.EndChangeCheck())
        {
            serializedObject.ApplyModifiedProperties();
        }
    }
}

Though as I said it usually makes more sense to create a property drawer for your custom type. For example If you have a struct like this:

[System.Serializable]
public struct DVector3
{
    public double x;
    public double y;
    public double z;
}

With a property drawer like this:

[CustomPropertyDrawer(typeof(DVector3))]
public class DVector3Drawer : PropertyDrawer
{
    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return base.GetPropertyHeight(property.FindPropertyRelative("x"), label);
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        var xProperty = property.FindPropertyRelative("x");
        var yProperty = property.FindPropertyRelative("y");
        var zProperty = property.FindPropertyRelative("z");
        Rect r = EditorGUI.PrefixLabel(position, label);
        float width = r.width / 3;
        r.width = width;
        EditorGUIUtility.labelWidth = 10f;
        EditorGUI.PropertyField(r, xProperty);
        r.x += width;
        EditorGUI.PropertyField(r, yProperty);
        r.x += width;
        EditorGUI.PropertyField(r, zProperty);
    }
}

You don’t have to do anything special. So when you use this type in a class it will show up as specified by the drawer:

public class SpaceObject : MonoBehaviour
{
    public DVector3 origin ;
    // [ ... ]
}

Here you get multi object editing and undo support out of the box.

Found the solution myself. The problem was that the struct I was trying to put in the interface wasn’t made serializable yet.

I just needed to put a [System.Serializable] in front of my struct declaration and it worked.

The code above works fine if that is considered.