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!

ShadowBox: Interactivity Toolkit Update – v1.2.0

ShadowBox: Interactivity Toolkit has been updated to version 1.2.0! This brings a variety of changes to the underlying code in the toolkit, in preparation to decouple ShadowBox into a separate framework.

The main changes include:

  • NEW! Module Configuration System – ShadowBox is evolving into a framework, and as such, will have a variety of planned (and unplanned) modules and integrations in the future. The new module system allows you to enable or disable specific modules as necessary for your project or scene(s).
  • Namespace Change – The primary namespace for ShadowBox has changed from Shadowed to ShadowBox. This better aligns with the directory structure and package name.
  • Directory Change – The primary directory for the Interactivity Toolkit module has changed.
  • NEW! Online Documentation – Online documentation for ShadowBox and its’ assorted modules can be found here. The documentation included therein is a work in progress, and is subject to change.
  • BUG SQUASH – Dome Colliders now properly work!
  • NEW! Extra Fields – Additional trigger options have appeared in both Dome Collider and Box Trigger Areas.
  • Editor Window and Inspector Theming – Reworked all existing editors and inspectors for ShadowBox and assorted modules to be more uniform and readable regardless of Unity Editor theme.

You can find ShadowBox: Interactivity Toolkit by clicking here!

ShadowBox: Interactivity Toolkit

Does your environment have props that could use a little boost? Doors that don’t budge, windows that won’t open, levers that are more akin to sconces? Perhaps you’ve got a desk with glued on drawers? We’ve been hard at work the past two weeks building an easy to use, simple way to add interactions to your environment, and would like to present the first few glimpses of ShadowBox: Interactivity Toolkit. Keep in mind that this is a work in progress, and may not represent the final product.

Features:

Doors: Perfect for trapdoors, drawbridges, dwelling doors, chests, and much more!
Windows and Shutters: Open a window! Hey! Close that window, pal! You’re letting a draft in!
Drawers: What good is a filing cabinet if it can’t hold files? In, Out. In, Out.
Portcullis Doors: When one door closes, another opens, or so the saying goes.
Switches and Levers: There is an evil necromancer crying, because nobody has pulled her lever. Pull the lever, Kronk!
Buttons: We all love a good button! Press the button! Go on! That was easy.

Procedural Goodness

I will admit it. Procedural World’s Gaia has me spoiled, but it is very large, and I simply did not want it in this project.

So where does that leave me? Hand painting and placing each environment prop, tree, grass patch, and flower individually? Forget that! After some perusing through available utilities, I settled upon a little number by Staggart Creations aptly named Vegetation Spawner.

Vegetation Spawner procedurally plants trees and places billboards, mesh, and grass vegetation prefabs depending on a number of factors, least of which is the terrain layer weight underneath. This works beautifully, even with a large library of environmental props (so long as you remember to tune the spawn settings, otherwise you’ll end up waiting 46 hours to spawn trees!).

However, by default, it only handled vegetation — I also wanted that procedural placement goodness for my rocks, sticks, mushrooms, cactii, logs, and more… so after a long days’ work, I managed to extend the functionality to cover any prop thrown at the utility – leading to a beautiful, randomized placement of environment prefabs in all of the right places.

Here’s a screenshot!

Procedural desert environment goodness!

When one door closes…

…this is guaranteed to open it back up! Today, I’m going to offer a peek at one of the in house editor tools our artists will be using soon! Introducing the Shadowed Door.

In reality, a door on a hinge will swing along a set radius to open or close. In a game, the door has to be told how to swing, otherwise it will instantaneously move from one position to another, defying all known laws of physics. This can be handled with a simple linearly interpolated movement, broken up over a set duration.

However, I think doors are a little more complex than simply swinging to and fro. They can lock, they can change direction before fully being opened or closed, and the hinges can exist pretty much anywhere.

As I began further fleshing out the functionality of a basic hinged door, I was soon overwhelmed by the inspector for the door component. So I set out to fix this cluster of variables, and am quite happy with the results. Take a look!