Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Extend Unity's built-in inspectors
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
/// <summary>
/// A base class for creating editors that decorate Unity's built-in editor types.
/// </summary>
public abstract class DecoratorEditor : Editor
{
// empty array for invoking methods using reflection
private static readonly object[] EMPTY_ARRAY = new object[0];
#region Editor Fields
/// <summary>
/// Type object for the internally used (decorated) editor.
/// </summary>
private System.Type decoratedEditorType;
/// <summary>
/// Type object for the object that is edited by this editor.
/// </summary>
private System.Type editedObjectType;
private Editor editorInstance;
#endregion
private static Dictionary<string, MethodInfo> decoratedMethods = new Dictionary<string, MethodInfo>();
private static Assembly editorAssembly = Assembly.GetAssembly(typeof(Editor));
protected Editor EditorInstance
{
get
{
if (editorInstance == null && targets != null && targets.Length > 0)
{
editorInstance = Editor.CreateEditor(targets, decoratedEditorType);
}
if (editorInstance == null)
{
Debug.LogError("Could not create editor !");
}
return editorInstance;
}
}
public DecoratorEditor (string editorTypeName)
{
this.decoratedEditorType = editorAssembly.GetTypes().Where(t => t.Name == editorTypeName).FirstOrDefault();
Init ();
// Check CustomEditor types.
var originalEditedType = GetCustomEditorType(decoratedEditorType);
if (originalEditedType != editedObjectType)
{
throw new System.ArgumentException(
string.Format("Type {0} does not match the editor {1} type {2}",
editedObjectType, editorTypeName, originalEditedType));
}
}
private System.Type GetCustomEditorType(System.Type type)
{
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var attributes = type.GetCustomAttributes(typeof(CustomEditor), true) as CustomEditor[];
var field = attributes.Select(editor => editor.GetType().GetField("m_InspectedType", flags)).First();
return field.GetValue(attributes[0]) as System.Type;
}
private void Init()
{
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var attributes = this.GetType().GetCustomAttributes(typeof(CustomEditor), true) as CustomEditor[];
var field = attributes.Select(editor => editor.GetType().GetField("m_InspectedType", flags)).First();
editedObjectType = field.GetValue(attributes[0]) as System.Type;
}
void OnDisable()
{
if (editorInstance != null)
{
DestroyImmediate(editorInstance);
}
}
/// <summary>
/// Delegates a method call with the given name to the decorated editor instance.
/// </summary>
protected void CallInspectorMethod(string methodName)
{
MethodInfo method = null;
// Add MethodInfo to cache
if (!decoratedMethods.ContainsKey(methodName))
{
var flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
method = decoratedEditorType.GetMethod(methodName, flags);
if (method != null)
{
decoratedMethods[methodName] = method;
}
else
{
Debug.LogError(string.Format("Could not find method {0}", method));
}
}
else
{
method = decoratedMethods[methodName];
}
if (method != null)
{
method.Invoke(EditorInstance, EMPTY_ARRAY);
}
}
public void OnSceneGUI()
{
CallInspectorMethod("OnSceneGUI");
}
protected override void OnHeaderGUI ()
{
CallInspectorMethod("OnHeaderGUI");
}
public override void OnInspectorGUI ()
{
EditorInstance.OnInspectorGUI();
}
public override void DrawPreview (Rect previewArea)
{
EditorInstance.DrawPreview (previewArea);
}
public override string GetInfoString ()
{
return EditorInstance.GetInfoString ();
}
public override GUIContent GetPreviewTitle ()
{
return EditorInstance.GetPreviewTitle();
}
public override bool HasPreviewGUI ()
{
return EditorInstance.HasPreviewGUI ();
}
public override void OnInteractivePreviewGUI (Rect r, GUIStyle background)
{
EditorInstance.OnInteractivePreviewGUI (r, background);
}
public override void OnPreviewGUI (Rect r, GUIStyle background)
{
EditorInstance.OnPreviewGUI (r, background);
}
public override void OnPreviewSettings ()
{
EditorInstance.OnPreviewSettings ();
}
public override void ReloadPreviewInstances ()
{
EditorInstance.ReloadPreviewInstances ();
}
public override Texture2D RenderStaticPreview (string assetPath, Object[] subAssets, int width, int height)
{
return EditorInstance.RenderStaticPreview (assetPath, subAssets, width, height);
}
public override bool RequiresConstantRepaint ()
{
return EditorInstance.RequiresConstantRepaint ();
}
public override bool UseDefaultMargins ()
{
return EditorInstance.UseDefaultMargins ();
}
}
@DenninDalke

This comment has been minimized.

Copy link

@DenninDalke DenninDalke commented Jun 17, 2016

On line 118, probably Debug.LogError(string.Format("Could not find method {0}", methodName)); is more appropriated.

@DenninDalke

This comment has been minimized.

Copy link

@DenninDalke DenninDalke commented Jun 17, 2016

Man, I'm facing a problem, it looks like that sometimes something is leaking and I start to receive some errors like this:

NullReferenceException: (null) UnityEditor.SerializedObject..ctor (UnityEngine.Object[] objs) (at C:/buildslave/unity/build/artifacts/generated/common/editor/SerializedPropertyBindings.gen.cs:74) UnityEditor.Editor.GetSerializedObjectInternal () (at C:/buildslave/unity/build/artifacts/generated/common/editor/EditorBindings.gen.cs:154) UnityEditor.Editor.get_serializedObject () (at C:/buildslave/unity/build/artifacts/generated/common/editor/EditorBindings.gen.cs:147) UnityEditor.GameObjectInspector.OnEnable () (at C:/buildslave/unity/build/Editor/Mono/Inspector/GameObjectInspector.cs:89)

Have you faced this before, or have any ideas how to prevent this happen?

@frideal

This comment has been minimized.

Copy link

@frideal frideal commented Jan 3, 2018

Error Exception:TargetException: Object does not match target type. will occurred when use this cool script.
When use reflection we pass the wrong instance to Invoke.

I think We should cache the inspector method with decoratedEditorType

private static Dictionary<System.Type, Dictionary<string, MethodInfo>> newDecoratedMethods = new Dictionary<System.Type, Dictionary<string, MethodInfo>>();

@usernameHed

This comment has been minimized.

Copy link

@usernameHed usernameHed commented Sep 21, 2019

same
Same problem as Dennin, 3 years later, and no fix ? ;) on unity 2019

@shaunus84

This comment has been minimized.

Copy link

@shaunus84 shaunus84 commented May 29, 2020

The argument exception error is due to Unity trying to serialise the same inspector, as it behaves as if two inspector windows are open. You can get around this by not implementing OnInspectorGUI in the DecoratorEditor.cs file but instead calling it through the CallInspectorMethod in a different function then calling this from either your extended class or anywhere within the call that the editor is making to draw GUI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.