-
-
Save liortal53/352fda2d01d339306e03 to your computer and use it in GitHub Desktop.
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 (); | |
} | |
} |
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?
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>>();
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.
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.
Verify Github on Galaxy. gid:bCBksuiNuPEUeaXeZkH2BC
On line 118, probably
Debug.LogError(string.Format("Could not find method {0}", methodName));
is more appropriated.