Over the course of development, I’m constantly finding cool features of Unity and c# that I never new existed. I’ve found some that are really useful, so I’m going to start sharing them regularly here. This week’s requires some prior knowledge. Definitely read up on ScriptableObjects and custom editors if you are unfamiliar. Without further ado:
Making a inspector for any ScriptableObject
Did you know any class deriving from ScriptableObject can be serialized and rendered in a custom Editor with a just a few lines of code? As it turns out, it is really easy:
// Creating an instance of a ScriptableObject
MyScriptableObject so = ScriptableObject.CreateInstance();
// Setting up the editor
Editor myEditor = Editor.CreateEditor(so);
// Drawing the editor
myEditor.OnInspectorGUI();
And that’s pretty much (almost) it! Some stuff you should know:
- In this example, myEditor.OnInspectorGUI() must be called in your EditorWindow or custom Editor’s OnInspectorGUI() method.
- Editor.CreateEditor() only needs to be called once, so you can absolutely call it once and cache the result. However, there is a catch: Editor.CreateEditor() throws an error when you call it from OnEnable. You will have to call it in your editor’s OnInspectorGUI() method, and if you want to be efficient you’ll have to use a boolean to make sure it is only called once.
Below is a full code example. In it, we’ll instance a ScriptableObject in a Monobehaviour. We’ll then write a custom inspector that will display the variables in the Monobehaviour, including those in the ScriptableObject:
ExampleScriptableObject.cs
using UnityEngine;
using System.Collections;
[System.Serializable]
public class ExampleScriptableObject : ScriptableObject {
// Some variables we also want displayed in the inspector
public float scriptableObjectFloat = 42f;
public int scriptableObjectInt = 42;
}
This will be the ScriptableObject we want to display in the inspector. It has two variables, scriptableObjectFloat and scriptableObjectInt, that will display in the inspector once we get the rest of our code working.
ExampleMonobehaviour.cs
using UnityEngine;
using System.Collections;
public class ExampleMonobehaviour : MonoBehaviour {
// Example variable that will display in the inspector
public string exampleMonobehaviourString = "foo";
/* The scriptable object we want to display in the inspector. Note the [HideInInspector] tag;
we don't actually need Unity to display the ScriptableObject, our code will handle that.*/
[HideInInspector]
public ExampleScriptableObject exampleScriptableObject = ScriptableObject.CreateInstance();
}
This is the Monobehaviour that we will build a custom inspector for. It has a variable called “exampleMonobehaviourString” that will be displayed in the inspector alongside the variables contained in the ScriptableObject we made, which is instanced in this class as “exampleScriptableObject”.
ExampleMonobehaviourEditor.cs
using UnityEngine;
using UnityEditor;
using System.Collections;
[CustomEditor(typeof(ExampleMonobehaviour))]
public class ExampleMonobehaviourEditor : Editor {
// We'll cache the editor here
private Editor cachedEditor;
/* using this boolean to keep track if whether cachedEditor has already been assigned too.
We're required to call Editor.CreateEditor() from OnInspectorGUI(), which is called often,
but we only really need to call Editor.CreateEditor() once. */
private bool cachedEditorNeedsRefresh = true;
public void OnEnable() {
// Resetting cachedEditor, and marking it to be reassigned
cachedEditor = null;
cachedEditorNeedsRefresh = true;
}
public override void OnInspectorGUI() {
// Grabbing the object this inspector is editing.
ExampleMonobehaviour editedMonobehaviour = (ExampleMonobehaviour)target;
//Checking if we need to get our Editor. Calling Editor.CreateEditor() if needed
if (cachedEditorNeedsRefresh) {
cachedEditor = Editor.CreateEditor(editedMonobehaviour.exampleScriptableObject);
//Ensuring this is only run once.
cachedEditorNeedsRefresh = false;
}
/* We want to show the other variables in our Monobehaviour as well, so we'll call
the superclasses' OnInspectorGUI(). Note this could also be accomplished by a call
to DrawDefaultInspector() */
base.OnInspectorGUI();
//Drawing our ScriptableObjects inspector
cachedEditor.DrawDefaultInspector();
}
}
Finally, the class that does the work! This class caches an editor in line 29 and draws it in line 41. This displays our ScriptableObject’s variables alongside those of ExampleMonobehaviour. It’s surprisingly useful! Here’s what it looks like in practice:
And that’s it! Leave your questions and comments down below!
Cheers,
-Mark