Manually Tracking a Custom Editor

I’ve been messing around with Unity’s custom editor system, and decided to try keeping Editors around after an object is unselected, so empty objects with no geometry have handles to select them, as well as other functionalities. I know this isn’t the intended use of the Editor’s functions, and would be better suited as a manager that stores all related objects, but this is more of an exercise than anything.

However, there’s been a problem. As far as I can tell, there are two ways to spawn an editor script with a Component as its target: first, the regular way is to select an object. I’ve tried doing this on load, cycling through and selecting each object and giving each a frame to register the new Editor. This is clunky with a few GameObjects and messy with many. The other way to add an Editor is to just use Editor.CreateEditor(). While this creates an object with the Component as its target and allows me to draw Handles using onSceneGUIDelegate, it doesn’t send the OnInspectorGUI() message.

As far as I can tell looking at the exposed C# source of Unity, inspector drawing is done by looking through a list of tracked editors through ActiveEditorTracker.sharedTracker.activeEditors. I’ve logged it, and it’s apparent that none of my custom editors are tracked, even the one that’s associated with the selected GameObject. This is a read-only array, and there’s no exposed functions I can see to add editors to this tracker manually. Is there some functionality to manually track and untrack active editors, or is this functionality entirely unexposed for the moment?

Your question intriques me because I’m all for these little tweaks to Unity as well.

From what I gather, you want to have an inspector-like view visible at all times, with no manual creation required and have certain gameobjects drawn with handles or other stuff in OnSceneGUI, even when they’re not selected. This can be done with EditorWindows. Below is the code which satisfies the above conditions.

The editor window targets CustomScript, which is a normal MonoBehaviour. The Editor window is only shown if there is at least one CustomScript in the Scene and disappears when the last one is deleted (because why have an editor if there’s nothing to edit). The editorWindow also acts as a second inspector for CustomScript, if a GO containing one is selected and shows something else when something else is selected. Although I added a menu item, no manual creation or removal is needed apart from adding a script to the scene.

Hope it gives you ideas, it was an interesting exercise regardless.

using UnityEngine;
using UnityEditor;

[InitializeOnLoad]
public class CustomEditorWindow : EditorWindow
{
    
    static CustomEditorWindow window;

    bool myBool = true;

    static CustomEditorWindow()
    {
        //this won't ever be removed
        EditorApplication.hierarchyChanged += CheckChanges;
    }

    // Add menu named "My Window" to the Window menu. Just for shows.
    [MenuItem("Window/My Window")]
    static void Init()
    {
        window = (CustomEditorWindow)EditorWindow.GetWindow(typeof(CustomEditorWindow));
        window.Show();
        
    }

    static void CheckChanges() {
        CustomScript[] r = GameObject.FindObjectsOfType<CustomScript>();

        if (r.Length == 0 && window != null)
        {
            window.Close();
            window = null;
        }
        else {
            Init();
        }

    }

    private void OnEnable()
    {
        SceneView.onSceneGUIDelegate += SceneGUI;
        
    }

    /*
     * This is updated 10 times per second or so.
     * If every frame is needed, add a function to the EditorApplication.update delegate in OnEnable.
     */
    void OnInspectorUpdate()
    {   
        
        Repaint();
    }

    void OnGUI()
    {
        GameObject target = Selection.activeGameObject;
        if (target != null && target.GetComponent<CustomScript>() != null){
            Editor e = Editor.CreateEditor(target.GetComponent<CustomScript>());
            e.OnInspectorGUI();
        }
        else {

            myBool = EditorGUILayout.Toggle("Toggle", myBool);
        }
    }

    void SceneGUI(SceneView view)
    {
        CustomScript[] go = GameObject.FindObjectsOfType<CustomScript>();

        foreach (CustomScript p in go)
        {
            Handles.Label(p.transform.position, "Visible label");
            Handles.DrawLine(p.transform.position, Vector3.zero);
            Handles.ArrowHandleCap(GUIUtility.GetControlID(FocusType.Keyboard), p.transform.position, Quaternion.LookRotation((Vector3.zero - p.transform.position).normalized, Vector3.back), 2, EventType.Repaint);            
        }
    }

    void OnDestroy()
    {
        SceneView.onSceneGUIDelegate -= SceneGUI;
    }

}