undo Back to Articles Index

Advanced C# Concepts for use in Unity Part 4

By: John Kidd Jr on 12/12/2022 01:56 AM

UnityEditor Scripting

The editor in Unity can be extended quite a bit to help with displaying data, providing easy to access test interfaces, and more.

Execute In Edit Mode

This attribute when added to a class allows it to run while still in Edit mode. Meaning the Update() function can run when we are editing things without needing us to run the actual game. Note that the update function is only run when something changes in the Editor, it doesn't run constantly like it would when the game is running.

Extending the Menu

When extending the menu, we don't want to have to add a Game Object and add a script to that object to make our changes. To that effect, it's imperitive we place the corresponding code in the correct place. Any C# code we write to make changes to the editor without the need of a GameObject need to be placed in a folder called "Editor" under the "Assets" folder. This is where Unity will look for any modifications we make.

Adding a simple menu option is easy, we just make a class and add a function for any menu item we want. The body of that function is what gets executed when someone clicks on that menu option. Here's a quick example that prints to the console when clicked:

using UnityEditor;
using UnityEngine;

public class MenuItems
{
    [MenuItem("Test/Run Test")]
    private static void HelloMenuItem()
    {
        Debug.Log("test complete");
    }
}

This is what it looks like in the Editor:

The test menu we just added

And when you click on it, we get this:

The result of clicking on our new item

We can also use a validator to test the active object by creating a second function and setting the isValidateFunction value to true in the MenuItem attribute. Here's a quick example on testing if a highlighted object is a Renderer:

// the false here says this is NOT the validation function, meaning
// this function gets executed when the validation returns true
[MenuItem("Test/Renderer Test", false, 1)]
private static void RendererTest()
{
    Debug.Log("test successful");
}

// this is our validation function, it must return a boolean value
// to let Unity know if the result was valid!
[MenuItem("Test/Renderer Test", true, 1)]
private static bool RendererValidation()
{
    return Selection.activeObject.GetType() == typeof(UnityEngine.Renderer);
}

That last value was the priority of the menu item. You can use this to order your menu items. Gaps of 10 or more creates a seperator.

Attributes

Attributes are the things we've been using all over that are enclosed in the square brackets []. These modify the code that follows in some way, that could be by making a change to the GUI used for that field, or really anything you can think of that would be useful. For this example, I'm going to focus on the Unity specific implementation, but base C# can do this as well in a less specialized way. See Microsoft's documentation for more information on using them in general.

The UnityEngine namespace contains an implementation for attributes called PropertyAttribute that we'll be using. We want to create a class to contain the code for our attribute, using the naming scheme of Property where is what we want to call this attribute and how we'd call it in code. We inherit from PropertyAttribute then we enter any code we need to. The constructor is called from the attribute like any other class as well. Here's a quick example:

using UnityEngine;

public class TestLabelAttribute : PropertyAttribute
{
    public string PropertyLabel { get; set; }

    public TestLabelAttribute(string propertyLabel)
    {
        PropertyLabel = propertyLabel;
    }
}

This attribute doesn't really do much, it just stores a label for the property that we could use elsewhere. This is what it would look like to actually use the attribute in code:

[TestLabel("attribute")] public string attributeName;

Notice that because we had a constructor that needed a string, we had to add a string into the attribute. Also note that you cannot pass in a variable, it MUST be a literal constant value.

One of the many cool things we can do with this, is use it to make changes to how to display a classes properties in the Unity Editor. For this we'd make a blank attribute, then we'd create a PropertyDrawer for that attribute. We'd then fill in the attribute for CustomPropertyDrawer and pass in the type of our attribute class. We'll go into detail for Property Drawers next, where you'll see examples on how to modify the GUI using a custom Property Drawer.

Property Drawer

Property Drawers are used to change how certain objects are drawn in the Unity Editor to make them look, feel, or just work better than how they would if drawn using the default method.

Let's first take a look at how to do this when our MonoBehaviour script contains a Serializable class storing some data. Here is the serializable object and the MonoBehaviour we're working with for this example:

using System;
using UnityEngine;

[Serializable]
public class WeaponElementalAttribute
{
    public string attributeName;
    public int affinity;
    public int baseDamage;
}

public class Weapon : MonoBehaviour
{
    public string displayName;
    public int baseDamage;
    public WeaponElementalAttribute[] attributes;
}

This renders in the Unity Editor by default like this:

The default view of a serializable object in Unity's editor

Now to customize it! For this example we're going to remove the field labels, and line them up to make it more compact. This wouldn't be a great experience for this example, but it shows several of the functions of creating a custom drawer that should give you an idea of what you can do.

We'll need to make a class and inherit from PropertyDrawer, then we'll implement a single required function: OnGui(), then inside that we'll put our logic to draw our new GUI. Let's look at the example:

using UnityEditor;
using UnityEngine;

// this attribute tells Unity what type this drawer is used on
[CustomPropertyDrawer(typeof(WeaponElementalAttribute))]
public class WeaponAttributeDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // wrap our draw code with Begin/End Property, this makes everything
        // work properly with SerializeProperty attribute
        EditorGUI.BeginProperty(position, label, property);

        // prefix each element with a label, and set the position
        // to reflect the new draw position. The GetControlId is used to get
        // the ID Unity uses internally for an object that cannot recieve focus
        position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);

        // save the current indent
        var indent = EditorGUI.indentLevel;
        // set indenting to 0 so the properties are right next to each other
        EditorGUI.indentLevel = 0;

        // calculate the rectangles to use for drawing our properties to the screen
        var affinityRect = new Rect(position.x, position.y, 40, position.height);
        var baseDamageRect = new Rect(position.x + 40, position.y, 40, position.height);
        var attributeNameRect = new Rect(position.x + 80, position.y, position.width - 90, position.height);

        // use the built in functions to actually draw everything to the screen
        // GUIContent.none removes the label from each property
        EditorGUI.PropertyField(affinityRect, property.FindPropertyRelative("affinity"), GUIContent.none);
        EditorGUI.PropertyField(baseDamageRect, property.FindPropertyRelative("baseDamage"), GUIContent.none);
        EditorGUI.PropertyField(attributeNameRect, property.FindPropertyRelative("attributeName"), GUIContent.none);

        // reset the indent to the default
        EditorGUI.indentLevel = indent;

        EditorGUI.EndProperty();
    }
}

Once we have this code in place, it now looks like this:

The custom view in Unity's editor

This is only scratching the surface of what you can do, Unity has a lot more documentation for this, so check out the official documentation here!

Bitmasking

Bitmasking is a cool concept that let's you store a series of related boolean switches in a single variable. The term itself is pretty old school, so if you want better results when searching for help in C# you'll want to search by the name Microsoft uses; bit flags. No matter what you want to call them though, the way you set them up and use them remains the same.

First you create a Enumeration, apply the [Flags] attribute, and ensure that each value is given a set value as a power of 2. You can indicate that value using any method you like: decimal, hexadecimal, binary, bit shifting... the choice is yours! Typical combinations of values can also be defined in the enumeration to make things easier on yourself. Let's check out an example:

[Flags]
public enum WeaponType
{
    Fists = 0x00,
    Sword = 0x01,
    Gun = 0x02,
    Gunblade = Gun | Sword,
    Hammer = 0x04,
    Staff = 0x08,
    Scythe = Staff | Sword
}

As you can see in the example, we defined a series of weapon types, including two that could be considered typical combinations (in reality we probably wouldn't combine the staff and sword to create a scythe though). Everything was numbered using hexadecimal, but we could do the same using any other numbering system (or a combination of multiple numbering systems if you hate yourself... please don't ever do that). I'll show a quick example of a few different numbering systems with the first few items above for a quick example:

[Flags]
public enum WeaponType
{
    Fists = 0, //decimal for 0
    Sword = 0x01, //hexadecimal for decimal 1
    Gun = 1 << 1, //bit shifting 1 by 1 place... which is 10 in binary or 2 in decimal
    Hammer = 0b100 //binary literal for decimal 4
}

Now that we have a flagged object setup, let's check out how to use it in code after a brief explanation. We can add multiple values from the enumeration together to get the value we want. To add them together we use the OR operator which is a single pipe character |. You've likely seen this symbol used before when writing if statements to just mean OR when doubled up. That operator is better known as the conditional OR operator which returns a single boolean value when evaluating two other boolean values. The OR operator returns a binary value that is the result of running the original value through the or logic gate. If you don't remember or don't know how the or logic gate, it works by comparing binary values in pairs and returning a 0 when both compared values are 0, and a 1 otherwise. Here's a quick look:

0110001110 | 1110001101 = 1110001111

As you can see from the example, when the two values are run against each other by a OR operation, the result gives you a result of everything that was a 1 in at least 1 of the 2 inputs. What this means for our flags is that we can add a value to a variable using the OR operator. You've seen this already above in the example where I showed certain values already combined!

    ...
    Sword = 0x01,
    Gun = 0x02,
    // the gunblade uses a bitwise OR to define it's value!
    Gunblade = Gun | Sword,
    ...

Now that we've seen how to add a flag, how do we remove one? It's simple in practice, but not as easy to explain. We're going to use a combination of two operators to do this. The first is called the bitwise complement operator which is denoted by the ~ and the second is the AND operator which is similar to the conditional AND operator in the same way as the ORs were, in this case just a single & shows that we are using the operator.

The bitwise complement operator works by returning the compliment of a value, that is it swaps all 1s for 0s and 0s for 1s, giving you the exact opposite... or compliment... to the original value. Here's a quick example of this:

~0110001110 = 1001110001

The AND operator works by running the value through an AND gate. If you don't know, or just need a refresher, the AND gate works by returning a 1 when both values are 1 and a 0 otherwise. Here's a quick example of that:

0110001110 & 1110001101 = 0110001100

By combining both of these operators we can effectively remove the flag we are looking to remove. Let's look at what this looks like in C# first, then an example using binary second so you can see what really happened:

var gunblade = WeaponType.Sword | WeaponType.Gun;

// for some reason we have to remove the gun flag
gunblade = gunblade & ~WeaponType.Gun;
// or a nicer shorthand
gunblade &= ~WeaponType.Gun;

Here's the same thing in binary:

Sword    = 0001
Gun      = 0010
~Gun     = 1101

gunblade = 0011

0011 & 1101 = 0001

Hopefully that binary example was easy to understand! The next thing we'll look at is much easier, and that is checking if a flag is enabled in a value. There is the classic binary way to do it, and a new way that's much easier. It's important to know both, so you aren't confused when reading someone else's code!

To check if a flag is enabled, we simply use the AND operator between our value and the value we're checking, then check if the result is equal to the flag we're checking. This works because as we learned above, the AND operator returns a 1 only if both values have a 1 in that position... so the only 1s it could possibly have in common are the ones from the value we're looking for! Here's a quick example:

var gunblade = WeaponType.Sword | WeaponType.Gun;

bool isGunbladeAGun = gunblade & WeaponType.Gun == WeaponType.Gun; // this will be TRUE

Now like I said, there's an easy way to do this as well:

var gunblade = WeaponType.Sword | WeaponType.Gun;

bool isGunbladeAGun = gunblade.HasFlag(WeaponType.Gun); // this will be TRUE

Scriptable Objects

Scriptable objects are a Unity exclusive feature that allows you to store data in a more efficient mannger than JSON. Some of the advantages are that the data can be input and manipulated in the Unity Editor and that you don't need to create or maintain any external tooling to use them. Note that these are not MonoBehaviour scripts, so they cannot be attached to a Game Object like regular scripts... they are only intended to hold data and nothing more.

Creating them is easy; create an empty class for your data, add in your fields, inherit from ScriptableObject, and add an attribute to tell Unity where to put it. Here's a quick example of this in action:

using UnityEngine;

[CreateAssetMenu(fileName = "weapon", menuName = "Weapon", order = 1)]
public class WeaponData : ScriptableObject
{
    public string displayName;
    public int baseDamage;
    public float damageMultiplier;
}

The attribute we're using here, CreateAssetMenu uses named parameters only, so you have to specify the parameter name to use them. The fileName parameter gives our new object a default name... just like any other object the name is immediately highlighted to give the designer a chance to change it. The next one, menuName is how our item will appear in the menu under Assets > Create. You can also put folders in there to better organize your objects using a / to note each folder, the last item will be the name you would click on the menu. The last one, order tells Unity what order to put the objects in.

This is what it looks like in the menu:

Example of the menu with a scriptable object

Once you create one using your menu item, you can modify the values like any other script in Unity:

Example of a scriptable object being edited

Please note that these objects should be considered the same as being static, meaning if you try to change a value in code anywhere from one of these objects, it'll change on everything else using it. To be safe, just use them to store initial values and then copy the values into another object to use and modify them.

undo Back to Articles Index