Code Snippet: In-Line Vector3Field for Editors

After fighting with Unity for the better part of an hour, attempting to align the label for a Vector3Field in an editor window on the same horizontal line as the Vector3Field, I decided to tackle it with a different approach.

Before and After using the new class.
using UnityEditor;
using UnityEngine;

public static class EditorUtilities
{
    /// <summary>
    /// Draws an inline Vector3Field with label.
    /// </summary>
    /// <param name="label">String to be used as the label.</param>
    /// <param name="input">Initial Vector3 input for this field.</param>
    /// <param name="options">GUILayoutOption parameters<</param>
    /// <see href="https://docs.unity3d.com/ScriptReference/EditorGUILayout.Vector3Field.html">EditorGUILayout.Vector3Field</see>
    /// <returns>Vector3 value defined by this inspector field.</returns>
    public static Vector3 Vector3Field(string label, Vector3 input, GUILayoutOption[] options = null)
    {
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.PrefixLabel(label);
        Vector3 ret = input;
        if (options != null) ret = EditorGUILayout.Vector3Field("", input, options);
        else ret = EditorGUILayout.Vector3Field("", input);
        EditorGUILayout.EndHorizontal();
        return ret;
    }

    /// <summary>
    /// Draws an inline Vector3Field with label.
    /// </summary>
    /// <param name="label">GUIContent to be used as the label.</param>
    /// <param name="input">Initial Vector3 input for this field.</param>
    /// <param name="options">GUILayoutOption parameters<</param>
    /// <returns>Vector3 value defined by this inspector field.</returns>
    /// <see href="https://docs.unity3d.com/ScriptReference/EditorGUILayout.Vector3Field.html">EditorGUILayout.Vector3Field</see>
    public static Vector3 Vector3Field(GUIContent label, Vector3 input, GUILayoutOption[] options = null)
    {
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.PrefixLabel(label);
        Vector3 ret = input;
        if (options != null) ret = EditorGUILayout.Vector3Field("", input, options);
        else ret = EditorGUILayout.Vector3Field("", input);
        EditorGUILayout.EndHorizontal();
        return ret;
    }
}

Using this code is easy, here’s how we call it:

Vector3 tmpVector = EditorUtilities.Vector3Field("Position:", referenceValue);
if(tmpVector != referenceValue) referenceValue = tmpVector;

Hope you found this useful!

Inspector with Tabbed Toolbar

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:

UsableGameObject MonoBehaviour without a custom inspector.
UsableGameObject MonoBehaviour without a custom inspector.

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!

Unity Tutorial Creator List

Hi guys! I have taken some time to compile a list of some of the tutorial and learning resources I’ve come across for the Unity Engine, amongst other things. We’ll kick it off with the beginner level!

Beginner Unity Tutorials

Brackeys – although now retired, Brackeys YouTube channel remains a source of viable content for the beginner!

ZenvaZenva Academy has a wide variety of free and paid learning courses for the beginner to intermediate Unity programmer.

CodeMonkeyCode Monkey is an experienced Indie Developer with a heavy focus on 2D tutorials for beginner and intermediate learners.

Intermediate Unity Tutorials

BlackthornprodBlackthornprod‘s YouTube channel has a lot of really cool tutorials covering all manner of systems for a plethora of games.

Infallible CodeInfallible Code’s videos include a lot of conversations with himself; he asks really good questions – then answers them, accordingly!

InfinityPBR InfinityPBR is better known for their kick-ass models on the Unity Asset Store, but they have a really informative YouTube channel as well!

LlamAcademyLlamAcademy has a lot of videos on NavMesh, AI, and an assortment of other topics.

TarodevTarodev has a colorful vocabulary and a lot of knowledge – check him out!

GameDevGuide – Matt from Game Dev Guide is another creator with a breadth of topics and some editor code sprinkled in!

Advanced Unity Tutorials

Dilmer ValecillosDilmer Valecillos‘s YouTube channel has a heavy focus on VR and XR programming.

Sebastian LagueSebastian Lague‘s channel is the result of an overactive imagination and a lot of brain power. Check this stuff out already!

Sharp AccentSharp Accent covers a lot of topics, and quite a few tutorial series for many different game types.

CatLikeCodingCatLikeCoding has a ton of shader and intermediate to advance system tutorials on their website.

Unity Shader Graph Tutorials

AETuts – AETuts is a channel covering intermediate and advanced Shader Graph recipes.

Gabriel Aguiar ProductionsGabriel Aguiar‘s YouTube channel focuses on Shader Graph recipes with a huge variety of effects!

Jetelly Jetelly‘s YouTube channel focuses on the VFX Graph within Unity, and is available en EspaƱol!

Game Development – General

The Game Architect – Adam Millard, The Game Architect‘s YouTube channel covers a vast variety of topics with a smattering of video game industry expertise throughout.

Mental Checkpoint Mental Checkpoint‘s videos go in depth into tricks, secrets, and all manner of practices within the games industry directly from an industry veteran.

Unity Editor: Custom Inspector Tooltips

If you ever find yourself designing an editor window and feel your custom inspector property needs a tooltip, this could come in handy:

public void GenerateTooltip(string text)
{
    var propRect = GUILayoutUtility.GetLastRect();
    GUI.Label(propRect, new GUIContent("", text));
}

Use it after the property you wish to add a tooltip to (I’m using a LayerMask in this example):

LayerMask groundLayer = EditorGUILayout.MaskField("Ground Detection Layer"), InternalEditorUtility.LayerMaskToConcatenatedLayersMask(animal.groundLayer), InternalEditorUtility.layers);
                    
GenerateTooltip("The layer(s) this animal can walk on.");

Before:

After: