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`?
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
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
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.