Editor Scripting Detailed Overview
Every component in Unity exhibits some kind of editor functionality: Public fields are exposed in the inspector panel, custom Inspector scripts can specialise the way that a components properties are presented and Editor windows can extend this functionality further by creating custom palettes that integrate into the unity IDE.
- Public fields appear within the inspector as appropriate controls; public enums become drop-down menus, strings become text boxes and booleans become checkboxes and more.
- Custom inspectors bind to an existing component to specialise the controls that are displayed in the inspector and implement additional logic that references the Unityeditor namespace; An example of this may be using the Handles class to create additional GUI controls in the editor window.
- Finally, you can define EditorWindows that bind to menu items in the IDE these can present controls in a ‘pallette’ and also implement additional logic for the editor window from within the UnityEditor namespace. The implementation of an in-editor tool for the Unity IDE is not limited to one of these approaches and many of the existing, more advanced tools such as NGUI combine all three.
When designing an in-editor tool for Unity3D it is important to consider the following points early in the process:
Refer to the existing documentation / tl;dr
There are at least two great documents on the mechanisms for making data persist here and here which I don’t intend to repeat, however these are the salient points:
When the Unity IDE switches from edit mode to game mode (and also when a script is edited forcing a recompilation), all the objects in the scene are serialised and destroyed. When the Assemblies reload the objects are de-serialised. Unity implements a custom serialisation mechanism that understands a subset of data types. Anything that subclasses UnityEngine.Object is naturally included (which means that classes that do not extend MonoBehaviour have to extend ScriptableObject.) public fields (and not properties) will be automagically serialised; private fields will be serialised if they are decorated with [SerializeField].
Consider the ‘scope’ of each script
MonoBehaviours can utilise any of the classes from within the UnityEngine namespace and are not intended to call on functionality from the UnityEditor namespace (unless they are decorated with [ExecuteInEditMode] they know nothing of the editor mode). MonoBehaviours have a lot of hooks called by the (unmanaged) counterpart in the Unity game engine including Update.
Editor Extensions that bind to a MonoBehaviour can utilise any of the classes from within the UnityEditor namespace, and also the UnityEngine namespace. Editor extensions have access to fewer hooks called by the Unity engine, for example there is no Update() method.
Editor Window scripts can utilise any of the classes from within the UnityEditor namespace. Since they hold the focus of the IDE they are not intended for accessing the Scene Window, for example you can only catch mouse events that relate specifically to its own window, whereas an editor extension can catch mouse events from the Scene Window.
Consider the lifecycle of each script
GameObjects in the Unity IDE represent unmanaged objects inside the Unity game engine, the scripts that you write are available to the unmanaged objects via the Assemblies that are managed by Unity IDE. To preserve the boundary between ‘editor time’ and ‘run time’ (being able to edit the scene at run time without breaking your scene in the editor) the GameObjects are destroyed and the assemblies are rebuilt when the mode of the IDE switches. MonoBehaviours become awake and enable when the IDE is in game state and disable and destroy when the IDE is in editor state. If you add an [ExecuteInEditMode] attribute the script will also awake and enable when you switch back from game state to editor state and disable and destroy when you enter game state from edit mode.
Editor scripts that bind to a MonoBehaviour enable when the component that they bind to is in focus in the inspector and disable when the focus is lost. This could occur either in ‘editor time’ or in ‘run time’.
Editor Window scripts awake and enable when they are opened in the IDE from their associated menu entry, they disable and destroy when they are closed.
Consider the implications for persistance
As we have already established Gameobjects in the Unity IDE represent unmanaged objects inside the Unity game engine, the scripts that you write are available to the unmanaged objects via the Assemblies that are compiled by Unity IDE. Every time a script is edited, a recompilation is required to rebuild the Assembly so that it contains the latest code, when the IDE switches from edit mode to game mode the assembly is also reloaded. To preserve the boundary between ‘editor time’ and ‘run-time’ components in the editor mode are serialised whereas the components in game mode are not. This means that changes you make at ‘run time’ don’t persist into ‘editor time’ but changes in ‘editor-time’ persist once you return from ‘run-time’.
A MonoBehaviour will serialise a public property and a private field that is decorated with [SerializeField] The MonoBehaviour is serialised and destroyed when the IDE switches from ‘edit-mode’ into ‘game-mode’. For a MonoBehaviour decorated with [ExecuteInEditMode] two separate instances of the Monobehaviour will be created one in ‘editor time’ (that is serialised and destroyed when the IDE switches) and it’s compiled counterpart in ‘run time’ (that is *not* serialised but *is* destroyed when the IDE switches back.)
The properties of an editor extension bound to a monobehaviour do not persist when the component goes in and out of focus, since no assembly reload is triggered the properties are not serialised, additionally enable and disable will be called every time the component goes in and out of focus. For the values to persist you need to refer to the properties of the bound component which are enabled and disabled only following assembly reloads. You can either access these properties through the ‘target’ property of the editor extension, or by using the SerializedProperty and SerializedObject mechanism. If you write an Editor Extension that binds to a component decorated with [ExecuteInEditMode] then you should also bear in mind that two separate instances are being bound, one at ‘editor time’ (that is serialised when the IDE switches) and one at ‘run time’ (that is *not* serialised when the IDE switches back.)
Since both the editor window and editor extension extend ScriptableObject they exhibit the same behaviour as a MonoBehaviour component with [ExecuteInEditMode]: The ‘editor time’ instance is destroyed and serialised when the IDE switches from edit mode to game mode and the ‘run time’ instance is destroyed (but not serialised) when the IDE switches back.