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!

Tutorial: Invoice Verification Discord Bot

Today I’m going to walk you through how to configure a Linux server to run your Unity Publisher Invoice Verification Discord Bot! This tutorial assumes you already have a Virtual Private Server up and running with Ubuntu 18.04 or later installed.

Step 1: Installing .NET 3.1 SDK

We will begin by installing the Microsoft .NET 3.1 SDK from their repositories, but first, we need to tell the package manager where those repositories are. Let’s do that now. *For newer versions of Ubuntu, make sure you are running a supported distribution by checking the list of supported distributions for .NET 3.1.

wget https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb

Next we will ensure everything is up to date, refresh the package information from the repositories, and install the .NET 3.1 SDK.

sudo apt-get update; \
  sudo apt-get install -y apt-transport-https && \
  sudo apt-get update && \
  sudo apt-get install -y dotnet-sdk-3.1

Step 1.1: Additional Utilities

Additionally, we will want to install the Screen utility so the bot can run in the background, and ensure the unzip utility is installed so that we can extract the bot from its’ archive.

sudo apt-get install -y screen unzip

Step 2: Extracting and configuring the bot.

Upload the bot to your server using the method of your choice, navigate to the folder in which it is located, and type the following:

unzip ShadowedBot.zip -d ShadowedBot && cd ShadowedBot/

After that, we will need to configure the bot! You will need the following information:

  • An Application Token from Discord
  • Your Unity Publisher API Key

Step 2.1: Discord Application Token

  1. Go to the Applications page on the Discord Developer portal.
  2. Press the New Application button.
  3. On the New Application page, enter your bot’s application name in the name field.
  4. Press the Create button.
  5. On the General Information page, under the Settings pane, select Bot.
  6. On the Bot page, add a username and display icon for your bot.
  7. Under Bot Permissions, select Manage Roles underneath General Settings, and Send Messages underneath Text Permissions.
  8. Underneath Username, you will find Token. Click Copy to copy your bot’s token – this is the application token necessary for the bot configuration. Do not lose this!

Step 2.2: Unity Publisher API Key

  1. Go to the Invoices page of the Unity Publisher Portal.
  2. Copy your API key. This is the apiKey necessary for the bot configuration. Do not lose this either!

Step 2.3: Adjusting the config.json file

Open the config.json file in your favorite text editor (Ours is JOE!) and change your configuration file to include your token, API key, server name, asset name(s), and role(s) to be assigned upon verification. Here’s a sample:

{
  "token": "DISCORDAPPLICATIONTOKENFORVERIFICATIONBOT",          // Your bot application token from Discord.
  "prefix": "!",        // The prefix to use for this bot (default: !verify) 
  "apiKey": "UNITYPUBLISHERAPIKEYFORINVOICEVERIFICATION",         // Your Unity Publisher API key!
  "serverName": "Shadowed Souls Studios",     // The name of your Discord server (example: Official Unity Discord)
  "assetRoles": {       // A dictionary containing the full name of your asset and the Discord role to assign.	
    "UMA Add-On for RPG Builder":"UMA Verified",
    "TOP_SECRET":"COMING_SOON"
  }
}

Step 3: Run the bot!

We will use the Linux Screen utility to help keep the bot running in the background. This will require you to send keyboard commands through your terminal if remotely connected, and will be denoted with an asterisk (*) when necessary.

Type the following commands into your terminal!

screen -S shadowedbot
dotnet ShadowedBot.dll
*CTRL+A+D (this releases the window into the background!)

And that’s it! If you followed these steps, you should be up and running! To verify an invoice, simply message the bot with the following command:

!verify ############

Replacing ############ with an invoice number.

That’s all for now! Thanks for reading!