Hi folks! Today I’ve been building out some editors, and felt like sharing something useful. The following snippet is a function that draws a tabbed toolbar. Useful for custom editors and inspectors!
public static int Toolbar(int value, string[] strings, int width = -1) => (width == -1) ? GUILayout.Toolbar(value, strings) : GUILayout.Toolbar(value, strings, GUILayout.Width(width));
Suppose we have a custom editor base class with some helper functions in it. It may look like this:
using UnityEngine;
using UnityEditor;
namespace Shadowed.Editors
{
public abstract class ShadowedEditor : Editor
{
private static readonly string[] _exclude = new string[] { "m_Script" };
public override void OnInspectorGUI()
{
serializedObject.Update();
DrawPropertiesExcluding(serializedObject, _exclude);
serializedObject.ApplyModifiedProperties();
}
private static void GenerateTooltip(string text)
{
var propRect = GUILayoutUtility.GetLastRect();
GUI.Label(propRect, new GUIContent("", text));
}
public static int Toolbar(int value, string[] strings, int width = -1) => (width == -1) ? GUILayout.Toolbar(value, strings) : GUILayout.Toolbar(value, strings, GUILayout.Width(width));
}
}
Now, let’s say we have a MonoBehaviour that has two primary functions – OnUse() and OnAfterUse().
When the object is used, OnUse() is called, which then calls OnAfterUse().
It may look something like this:
using UnityEngine;
using UnityEngine.Events;
namespace Shadowed
{
public class UsableGameObject : MonoBehaviour
{
public AudioSource aud;
public AudioClip OnUseAudio;
public AudioClip OnAfterUseAudio;
public UnityEvent OnUseEvent = new UnityEvent();
public UnityEvent OnAfterUseEvent = new UnityEvent();
public void OnUse()
{
if(aud != null && OnUseAudio != null)
{
aud.clip = OnUseAudio;
aud.Play();
}
OnUseEvent?.Invoke();
OnAfterUse();
}
private void OnAfterUse()
{
if(aud != null && OnAfterUseAudio != null)
{
aud.clip = OnAfterUseAudio;
aud.Play();
}
OnAfterUseEvent?.Invoke();
}
}
}
Before applying a custom inspector, that component looks like this:

Our next step is to make an ‘Editor’ folder if one does not exist. Any scripts in this folder do not get included when your game is built, and execute solely within Unity Editor.
Within the Editor folder, create a new C# script named ‘UsableGameObjectInspector’. Inside the file, your code might look something like this:
using UnityEditor;
using UnityEngine;
namespace Shadowed.Editors
{
[CustomEditor(typeof(UsableGameObject), true)]
public class UsableGameObjectInspector : ShadowedEditor
{
private int currentTab = 0;
private string[] tabStrings = new string[] { "On Use", "On After Use" };
private UsableGameObject t;
public override void OnInspectorGUI()
{
// Get a reference to the current UsableGameObject via type casting.
t = target as UsableGameObject;
// Draws a toolbar using tabStrings[] for the tab names, and keeps track of the active tab using currentTab.
currentTab = Toolbar(currentTab, tabStrings);
// Switch statement to draw the content depending on the selected tab.
switch(currentTab)
{
case 0:
default:
DrawOnUse();
break;
case 1:
DrawAfterUse();
break;
}
}
private void DrawOnUse()
{
// Increase the indent level for this inspector.
EditorGUI.indentLevel++;
// Begin the vertical layout group.
EditorGUILayout.BeginVertical();
// Declare a reference to the OnUseEvent UnityEvent on the selected UsableGameObect.
var e = serializedObject.FindProperty("OnUseEvent");
// Draw a field to display or set the reference above.
EditorGUILayout.PropertyField(e, true);
// Apply any modified data to the game object.
serializedObject.ApplyModifiedProperties();
// Add a space.
EditorGUILayout.Space();
// Begin a horizontal layout group
EditorGUILayout.BeginHorizontal();
// Create the space for a new label with tooltip.
var cLbl = new GUIContent("On Use Audio", "Plays when this Usable Game Object is being used.");
// Draw a field to display or set the OnUseAudio AudioClip on the selected UsableGameObject
t.OnUseAudio = (AudioClip)EditorGUILayout.ObjectField(cLbl, t.OnUseAudio, typeof(AudioClip), true);
// End the horizontal layout group
EditorGUILayout.EndHorizontal();
// End the vertical layout group
EditorGUILayout.EndVertical();
// Decrease the indent level for this inspector.
EditorGUI.indentLevel--;
}
private void DrawAfterUse()
{
// Increase the indent level for this inspector.
EditorGUI.indentLevel++;
// Begin the vertical layout group.
EditorGUILayout.BeginVertical();
// Declare a reference to the OnAfterUseEvent UnityEvent on the selected UsableGameObect.
var e = serializedObject.FindProperty("OnAfterUseEvent");
// Draw a field to display or set the reference above.
EditorGUILayout.PropertyField(e, true);
// Apply any modified data to the game object.
serializedObject.ApplyModifiedProperties();
// Add a space.
EditorGUILayout.Space();
// Begin a horizontal layout group
EditorGUILayout.BeginHorizontal();
// Create the space for a new label with tooltip.
var cLbl = new GUIContent("On After Use Audio", "Plays after this Usable Game Object has being used.");
// Draw a field to display or set the OnAfterUseAudio AudioClip on the selected UsableGameObject
t.OnAfterUseAudio = (AudioClip)EditorGUILayout.ObjectField(cLbl, t.OnAfterUseAudio, typeof(AudioClip), true);
// End the horizontal layout group
EditorGUILayout.EndHorizontal();
// End the vertical layout group
EditorGUILayout.EndVertical();
// Decrease the indent level for this inspector.
EditorGUI.indentLevel--;
}
}
}
After saving this file, your inspector should look like the following image!

I hope you had fun reading this article, and learned something useful!
You must be logged in to post a comment.