Search Unity

Extending (instead of replacing) built-in Inspectors

Discussion in 'Immediate Mode GUI (IMGUI)' started by Cobo3, May 29, 2016.

  1. Cobo3

    Cobo3

    Joined:
    Jun 16, 2013
    Posts:
    67
    Hello!

    I would like to share a smart solution for extending custom inspectors instead of overriding and replacing the built-ins. How would you, for instance, do something like this?
    Extended Transform Inspector.jpg

    When write your own inspector for a built-in class, you have to make sure to replicate its same inspector (kind of what Unify wiki does). That, however, is not always an easy solution.

    That inspector uses the built-in editor to show the local space coordinates and then expands that functionality to show world coordinates and some extra stuff.

    And here's a snippet of the example above :)

    Code (CSharp):
    1. [CustomEditor(typeof(Transform), true)]
    2.     [CanEditMultipleObjects]
    3.     public class CustomTransformInspector : Editor {
    4.  
    5.         //Unity's built-in editor
    6.         Editor defaultEditor;
    7.         Transform transform;
    8.  
    9.         void OnEnable(){
    10.             //When this inspector is created, also create the built-in inspector
    11.             defaultEditor = Editor.CreateEditor(targets, Type.GetType("UnityEditor.TransformInspector, UnityEditor"));
    12.             transform = target as Transform;
    13.         }
    14.  
    15.         void OnDisable(){
    16.             //When OnDisable is called, the default editor we created should be destroyed to avoid memory leakage.
    17.             //Also, make sure to call any required methods like OnDisable
    18.             MethodInfo disableMethod = defaultEditor.GetType().GetMethod("OnDisable", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
    19.             if (disableMethod != null)
    20.                 disableMethod.Invoke(defaultEditor,null);
    21.             DestroyImmediate(defaultEditor);
    22.         }
    23.  
    24.         public override void OnInspectorGUI(){
    25.             EditorGUILayout.LabelField("Local Space", EditorStyles.boldLabel);
    26.             defaultEditor.OnInspectorGUI();
    27.  
    28.             //Show World Space Transform
    29.             EditorGUILayout.Space();
    30.             EditorGUILayout.LabelField("World Space", EditorStyles.boldLabel);
    31.  
    32.             GUI.enabled = false;
    33.             Vector3 localPosition = transform.localPosition;
    34.             transform.localPosition = transform.position;
    35.  
    36.             Quaternion localRotation = transform.localRotation;
    37.             transform.localRotation = transform.rotation;
    38.  
    39.             Vector3 localScale = transform.localScale;
    40.             transform.localScale = transform.lossyScale;
    41.  
    42.             defaultEditor.OnInspectorGUI();
    43.             transform.localPosition = localPosition;
    44.             transform.localRotation = localRotation;
    45.             transform.localScale = localScale;
    46.             GUI.enabled = true;
    47.         }
    48.     }
    Note: I am still to figure out a way to show and be able to modify world position (I mean to do it properly. Being able to multi-inspect, Undo and redo and general stuff), that's why I made it read-only.



    I found this to be an easy and clever solution while making my Component Tool Panel editor extension. And it can also be used to expand on GameObject inspector:
    ComponentToolPanel.jpg

    I really hope this helps someone =)
     
  2. crispybeans

    crispybeans

    Joined:
    Apr 13, 2015
    Posts:
    210
    I were looking to do something like what you achieve of making a copy of the "default inspector" and calling it's ongUI for a long time - Thanks!
     
  3. nukadelic

    nukadelic

    Joined:
    Aug 5, 2017
    Posts:
    78
    Neat hack, used your code and it functions flawlessly. So I found the following repo ( Link ) containing all unity editors with the goal to extend the TextEditor but for some reason it won't register

    [CustomEditor(typeof (UnityEngine.UI.Text), true)]

    Any ideas why its not registering ?
     
  4. LazloBonin

    LazloBonin

    Joined:
    Mar 6, 2015
    Posts:
    813
    So far I can only get it to work with Transform... BoxCollider, Rigidbody, MeshRenderer aren't working.

    I'm guessing there is some kind of priority check, but I'd have to dig in the reference source to figure out where it gets determined and if there's anything we can do about it. So far, I don't see much of a difference between Transform and the others that would cause them to fail.

    If you ever figured it out, I'm curious!
     
  5. LazloBonin

    LazloBonin

    Joined:
    Mar 6, 2015
    Posts:
    813
    Hm, after much debugging, it seems that just entering play mode made it work. The script recompile somehow doesn't seem to clear the native type cache for custom editors.
     
    Cobo3 likes this.
  6. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,331
    This pattern works really great! I use it extensively in Power Inspector, first wrapping the default editors inside custom drawers and then layering all kinds of enhancements on top. Well, actually I only do this when custom editors exist for the targets, otherwise I do even more crazy things, recreating the look of editors without actually using them at all, so that I can customize things to an even greater degree :D

    One thing to note is that you do not need to call OnDisable or OnDestroy manually for the editor before destroying it, since those get called automatically whenever UnityEngine.Objects are destroyed. So you can just skip that step.

    Another thing to be aware of when customizing the editors of assets is AssetImporters. Basically you can call Editor.CreateEditor for either the targets themselves or their asset importers, and get two completely different results. For example the editor you see when inspecting a texture is actually the TextureImporterInspector and not the TextureInspector.
     
  7. Wolfram

    Wolfram

    Joined:
    Feb 16, 2010
    Posts:
    261
    THANK you for your code example and the Editor.CreateEditor trick!
    After sifting through tons of posts and answers and official docs and even the reference implementation, nowhere can be found why when trying to override/extending the default Inspector for Transform and calling base.OnInspectorGUI(), like one would expect, the look is COMPLETELY DIFFERENT from Unity's Transform Inspector (for starters, it suddenly shows the rotation FIRST, and then the position...).

    Your method does exactly what I want: show Unity's actual Inspector,including the relatively new "contrained scale" lock icon, including support for the blue highlighting for prefab overrides, simply extended by my additional elements.

    NOTE: Haven't tested your exact version, but from my understanding to actually support multi-object editing you'd have to use SerializedProperty, instead of accessing target directly. This could explain your troubles with the world position part.
     
    Last edited: Feb 24, 2022
  8. g4ma

    g4ma

    Joined:
    Dec 18, 2018
    Posts:
    32
    Thanks a lot for this neat hack!

    Something I did not realise immediately and that could help others: if the call to GetType() fails and returns null, then Editor.CreateEditor() will simply try to invoke the default editor for this type, which is...ourself.
    This results in a infinitely recursive call and a nasty stack overflow that may crash Unity or simply hang it.

    So I advise to check the type first and maybe throw or Debug.Log():
    Code (CSharp):
    1. string foundEditorType = Type.GetType("UnityEditor.TransformInspector, UnityEditor")
    2. if(!string.IsNullOrEmpty(foundEditorType))
    3. {
    4.   defaultEditor = Editor.CreateEditor(targets, foundEditorType);
    5. }
     
  9. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,331
    Alternatively could also fallback to using the default Editor.

    Code (CSharp):
    1. Type fallbackEditorType = typeof(Editor).Assembly.GetType("UnityEditor.GenericInspector");
    Of course this could fail as well, so checking that the type is not null before calling Editor.CreateEditor is still good practice.
     
  10. rob11

    rob11

    Joined:
    Mar 7, 2017
    Posts:
    59
    Thanks everyone for this thread, works like a charm ! Well... almost !

    I have successfully extended the ModelInspector class, but I lost the Object Preview!

    Been playing around with this for a while, anyone knows how I could plug the mesh preview back in ?

    With my custom inspector :

    upload_2022-8-3_15-24-29.png

    Default inspector : (Preview is back)

    upload_2022-8-3_15-25-24.png

    Script :
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Reflection;
    5. using UnityEditor;
    6. using UnityEngine;
    7.  
    8. [CustomEditor(typeof(Mesh), true)]
    9. [CanEditMultipleObjects]
    10. public class CustomMeshInspector : Editor
    11. {
    12.     //Unity's built-in editor
    13.     Editor defaultEditor;
    14.     Mesh mesh;
    15.  
    16.     void OnEnable(){
    17.         //When this inspector is created, also create the built-in inspector
    18.         defaultEditor = Editor.CreateEditor(targets, Type.GetType("UnityEditor.ModelInspector, UnityEditor"));
    19.         mesh = target as Mesh;
    20.  
    21.     }
    22.  
    23.     void OnDisable(){
    24.         //When OnDisable is called, the default editor we created should be destroyed to avoid memory leakage.
    25.         //Also, make sure to call any required methods like OnDisable
    26. MethodInfo disableMethod = defaultEditor.GetType().GetMethod("OnDisable", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
    27.         if (disableMethod != null)
    28.             disableMethod.Invoke(defaultEditor,null);
    29.         DestroyImmediate(defaultEditor);
    30.     }
    31.  
    32.     public override void OnInspectorGUI(){
    33.         EditorGUILayout.LabelField("Optimization Settings", EditorStyles.boldLabel);
    34.         if (GUILayout.Button("Reduce Mesh"))
    35.         {
    36.             Debug.Log("REDUCING MESH! ");
    37.         }
    38.         defaultEditor.OnInspectorGUI();
    39.     }
    40. }
    Thank you for any help, really appreciated.

    For reference, this is the ModelInspector class : https://github.com/Unity-Technologi...aster/Editor/Mono/Inspector/ModelInspector.cs
     
  11. xodennisxo

    xodennisxo

    Joined:
    Mar 25, 2020
    Posts:
    24
    have you ever tried to add these function overrides in your CustomMeshInspector class?
    public override void DrawPreview(Rect previewArea)
    {
    defaultEditor.DrawPreview(previewArea);
    }

    public override bool HasPreviewGUI()
    {
    return defaultEditor.HasPreviewGUI();
    }
     
  12. berber_hunter

    berber_hunter

    Joined:
    Apr 30, 2021
    Posts:
    14
    Is there anything similar for UI_Toolkit