Skip to content

Instantly share code, notes, and snippets.

@liortal53
Last active April 2, 2024 17:10
Show Gist options
  • Star 69 You must be signed in to star a gist
  • Fork 15 You must be signed in to fork a gist
  • Save liortal53/352fda2d01d339306e03 to your computer and use it in GitHub Desktop.
Save liortal53/352fda2d01d339306e03 to your computer and use it in GitHub Desktop.
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
Copy link

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
Copy link

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
Copy link

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

@shaunus84
Copy link

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.

@CrowbarSka
Copy link

Thanks for this helpful script!

I'm trying to override the GameObject editor and followed your tutorial but I keep getting "Could not find method" errors spammed in the console. I traced it back to: CallInspectorMethod("OnSceneGUI"); I commented this part out because it doesn't seem necessary I think? But just thought I'd let you know.

This is in Unity 2020.3.12f1.

@JingFengJi
Copy link

image
image

Unity Version 2020.3.9f1c1
Cannt use for RectTransform Component , the editorInstance is null

@Shohag503
Copy link

Verify Github on Galaxy. gid:bCBksuiNuPEUeaXeZkH2BC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment