Do Custom Inspectors Support Inheritance?

Let's say I have the following hierarchy set up:

BaseClass -> MyClass

If I make a custom inspector for BaseClass, can I get it to display for `MyClass`?

I’m not sure if/when this was changed, but you can explicitly tell Unity to use a particular Editor for inherited classes:

e.g.

[CustomEditor( typeof( BaseClassName ), true )]

Hope this helps!

Well you can fix this easily by overwriting the OnInspectorGUI() in your BaseClass. If I were you I’d create a BaseClassEditor script which is the CustomEditor for the BaseClass and a MyClassEditor script which inherits from BaseClassEditor. To put it in your example:

// C#
[CustomEditor(typeof(BaseClass))]
public BaseClassEditor : Editor
{
    public override void OnInspectorGUI()
    {
        // Put YOUR custom inspector code here for the base class
    }
}

[CustomEditor(typeof(MyClass))]
public class MyClassEditor : BaseClassEditor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        // Additional code for the derived class...
    }
}

This should work just fine. (not tested myself but am about to…)
Hope this is what you’re looking for…

Of course you NEED to define the [CustomEditor(typeof(MyClass))], if not unity will draw the default inspector even if you have a CustomEditor for the base class…

cheers!

I ran into this and you can make it work without having to write a custom inspector for every derived class - not sure how wise it is, but this works in U3/U4 - certainly totally undocumented :slight_smile:

using UnityEngine;
using System.Collections;
using UnityEditor;
using System;
using System.Reflection;
using System.Linq;


[InitializeOnLoad] //Make sure this code runs every time the editor is updated
public class MagicInspectors : EditorWindow {

	
	static MagicInspectors()
	{
            //Get access to the UnityEditor assembly
		var asm = Assembly.GetAssembly(typeof(UnityEditor.CustomEditor));
            //Use Linq to find the CustomEditorAttribute type
		var cea = asm.GetTypes().FirstOrDefault(t=>t.Name == "CustomEditorAttributes");
            //Get access to the method that is called to find a custom editor for a type - this 
            //caches the results, so it has to happen before we play with the lists
		var findCustomEditor = cea.GetMethod("FindCustomEditorType", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            //Call it
		findCustomEditor.Invoke(null, new object [] { new UnityEngine.Object(), false });
            //Find the MonoEditorType class so we can make instances of it later
		var next = asm.GetTypes().FirstOrDefault(t=>t.Name .Contains("MonoEditorType"));
		var inst = Activator.CreateInstance(next);
            //Get the field in that class which is the type of the inspector to use
		var inspectorType = next.GetField("inspectorType");
            //Get the field in that class which is the type to inspect using this inspector
		var inspectedType = next.GetField("inspectedType");
            //Get the custom editors field which is the cache in CustomEditorAttribute
		var editorsField = cea.GetField("m_CustomEditors", BindingFlags.Static | BindingFlags.NonPublic);
            //Get the current list (it's an ArrayList)
		var editors = editorsField.GetValue(null) as ArrayList;
            //Get the current list of multi item editors
		var multiEditorsField = cea.GetField("m_CustomMultiEditors", BindingFlags.Static | BindingFlags.NonPublic);
		var multiEditors = multiEditorsField.GetValue(null) as ArrayList;
		
	        //Now its time to get all of the inspectors we've defined
		//Get all of the current assemblies loaded
		var types = AppDomain.CurrentDomain
			.GetAssemblies()
                    //Get all of the types in those assemblies
			.SelectMany(a=>a.GetTypes())
                    //Which have a CustomEditor attribute
			.Where(t=>t.IsDefined(typeof(CustomEditor), true))
                    //Get the type that this CustomEditor edits
			.Select(t=>new { editor = t, inspected = (Attribute.GetCustomAttribute(t, typeof(CustomEditor), false) as CustomEditor).m_InspectedType}).ToList();

            //Now look for types that are the type edited or its subclasses
		var usableTypes = AppDomain.CurrentDomain
			.GetAssemblies()
			.SelectMany(a=>a.GetTypes())
                    //For all of the types, get the custom inspector for which they are assignable
                    //In other words find an inspector (or null) for which this type can be downcast to
			.Select(t=>new { editable = t, custom = types.FirstOrDefault(e=>e.inspected.IsAssignableFrom(t)) })
                    //Make sure we only have valid ones
			.Where(r=>r.custom != null);

            //Now update the internal cache with these new types

		foreach(var newEditor in usableTypes)
		{
                    //Create a new instance of the internal structure that represents the relationship
			var editorInstance = Activator.CreateInstance(next);
                    //tell it which inspector to use
			inspectorType.SetValue(editorInstance, newEditor.custom.editor);
                    //tell it which type to inspect from the list we made above
			inspectedType.SetValue(editorInstance, newEditor.editable);
                    //Add it to multiEditors if it supports it
			if(newEditor.custom.editor.GetType().IsDefined(typeof(CanEditMultipleObjects), false))
				multiEditors.Add(editorInstance);
			        //Add it to ordinary editors always
			editors.Add(editorInstance);
		}
		
		
	}
	
}

This doen’t work in my case.
I have a custom monobehaviour, let’s say MyMonoBehaviour derived from MonoBehaviour.
Users should be able to derive their new scripts from MyMonoBehaviour.

In the inspector I want to add some Buttons that belong to MyMonoBehaviour.

Problem is, I can not force users to write a custom inspector for their scripts and to call the base class inspector!

So is there any way to achieve my goal either?
I don’t see one at the moment. It would work if the default Inspector would call it’s base class inspectors. But unfortunately it seems not to do that?

Any ideas?

it’ 2019, my research corroborates the findings, It doesn’t work out of the box as you would hope. The workarounds appear possible, but more complicated than worth the effort and probably less stable.

The compiler does not like this solution:
[CustomEditor(typeof(BaseScript))] public abstract class BaseScriptEditor : Editor { protected abstract void OnDerivedInspectorGUI(); private void OnInspectorGUI() { // do YOUR custom stuff here OnDerivedInspectorGUI(); } }
but it does look quite compelling, but the warnings bug me :wink:

There are alternative ways to make it customizable as well, Copy/paste seems more stable for the kind of complexity I need at the moment unless there’s a better solution…

I was initially having a lot of trouble getting a child of a custom editor to function, and from the looks of it, other people were having a similar problem.

.

I believe it comes down to this – in order for whatever reflection system Unity uses for event functions to call OnSceneGUI() to call them at all, the child object needs to have access to the event functions. This means that even if there are no references or inheritance whatsoever to the parent event functions in the text of the child editor, the parent must mark the event functions as protected and not private. The system accesses the event functions through the child, so the child needs access.

.

Here’s a stripped-down example of something I was working on, which replaces the in-editor handles with new, custom ones, and then updates the component’s display whenever those handles are moved. There’s a base Editor for DummyComponent, and a child editor that extends it for DummyChildComponent.

public class DummyComponent: MonoBehaviour {
    public float Value = 10;

    public virtual void UpdateDisplay() {
        Debug.Log($"Value: {Value}");
    }
}

public class DummyChildComponent: DummyComponent {
    public float Value2 = 11;

    public override void UpdateDisplay() {
        Debug.Log($"Value1: {Value} Value2: {Value2}");
    }
}

[CustomEditor(typeof(DummyComponent))]
public class DummyEditor: Editor {
    protected DummyComponent Dummy;

    private SerializedProperty _value;

    /// <summary>
    /// Sets serialized properties, disables standard transform tools, and adds an update method to the Undo callback
    /// </summary>
    protected void OnEnable() {
        Dummy = (DummyComponent) target;
        Tools.hidden = true;
        Undo.undoRedoPerformed += UpdateTarget;

        SetSerializedProperties();
    }

    /// <summary>
    /// Reenables standard transform tools, removes Undo callback
    /// </summary>
    protected void OnDisable() {
        Tools.hidden = false;
        Undo.undoRedoPerformed -= UpdateTarget;
    }

    /// <summary>
    /// Check for handle updates, apply them to the serialized properties. Then apply THOSE changes to the actual properties.
    /// </summary>
    protected void OnSceneGUI() {
        UpdateSerializedProperties();

        if (serializedObject.hasModifiedProperties) {
            serializedObject
                .ApplyModifiedProperties(); // apply serializedproperty changes to actual properties, record an entry in the undo stack
            UpdateTarget();
        }
    }

    /// <summary>
    /// Update the target object based on changes to handle values
    /// </summary>
    protected void UpdateTarget() {
        Dummy.UpdateDisplay();
    }

    /// <summary>
    /// Tell the serial handler what properties we want to keep track of
    /// </summary>
    protected virtual void SetSerializedProperties() {
        _value = serializedObject.FindProperty("Value");
    }

    /// <summary>
    /// Create handles, update our serialized properties based on those handle values
    /// </summary>
    protected virtual void UpdateSerializedProperties() {
        _value.floatValue = Handles.RadiusHandle(Quaternion.identity, Dummy.transform.position, Dummy.Value);
    }
}

[CustomEditor(typeof(DummyChildComponent))]
public class DummyChildEditor: DummyEditor {
    private SerializedProperty _value2;
    private static Quaternion _rotation = Quaternion.AngleAxis(45, Vector3.forward);

    protected override void SetSerializedProperties() {
        base.SetSerializedProperties();
        _value2 = serializedObject.FindProperty("Value2");
    }

    protected override void UpdateSerializedProperties() {
        base.UpdateSerializedProperties();
        _value2.floatValue =
            Handles.RadiusHandle(_rotation, Dummy.transform.position, ((DummyChildComponent) Dummy).Value2);
    }
}

I hope this rather long post is of use to somebody! The way Unity handles events, serialization, reflection, etc. can be very confusing, so I imagine I may not have been the only one with this particular problem, and hopefully this example is useful to others with similar issues. @ematsuno this may be the issue you were having.

Please note that the Serialization system is kind of a confusing mess in its own right, and it may work better for your application to use the Undo class instead. (For example, transform.position doesn’t seem to be accessible from the serialization system)

The “,true” enabled this to work with the base class for me, thanks.