Custom drawer in unity3d to pick an enum – event tutorial followup

  • Part 1: basics event system
  • Part 2: event picker drawer for in-editor usage [you are here]
  • Part 3: debugging with platform dependant compilation
  • Part 4: multiple dispatchers
  • Part 5: optimization

For today’s tutorial we’re going to make something that’s really comfortable to use: a dropdown menu to select an event. We’ll do that by creating anEventPickerclass and a custom drawer by estending thePropertyDrawerclass thatUnityEnginekindly gives us.

First, well’need aEventPickerclass. This class will actually hold the data inside the game, and will not in any way define the editor UI. It’s the data for which we’re going to define a Custom Drawer in Unity3d, not the drawer itself.

custom drawer in unity3d
let’s make a custom drawer

The EventPicker class

To function with the Event system we defined in last tutorial we’ll need for it the ability to read which event channel and which specific event have been selected. It also needs to allow us to identify the specific type of enum to which each channel will be associated with, so that later the custom drawer can use this information to draw the appropriate dropdown menu. Therefore we’ll need at least :

  • one field to store the selected channel
  • one field to store the selected event for each channel
  • one property to give access to the actual currently selected event to any class that will actually use the event picker.
using UnityEngine;

[System.Serializable]
public class EventPicker
{
    public const string channelVarName = "channel";
    public eventChannels channel;

    public inGameChannelEvents inGame;
    public menuChannelEvents menu;

    public System.Enum Selected
    {
        get
        {
            switch (channel)
            {
                case eventChannels.menu: return menu;
                case eventChannels.inGame: return inGame;
                default:
                    Debug.LogError("EventPicker was passed an unimplemented channel!!! " + channel.ToString());
                    break;
            }
            return channel;
        }
    }


}

Sadly, this means that each time a channel is added inChannelEnumsclass, it will need to be added here too. Also, since we’re going to need a Drawer, we don’t have the freedom to choose whatever variable names we want to pick since it relies on getting that name as a string.

String-run-fools
A string! Run, you fools!

We could solve this problem with C# reflection but that’s a deep topic that I’d rather address another time. For now, let’s just use a strong naming convention and have the the event-storage variables use the same name as the channel, while we addchannelVarNameto keep thechannelvariable name as a constant string field near the variable whose name it stores, so that whenever we may need to rename it we can just as easily copy-paste that name inside the string.

TheSelectedproperty is just a switch that returns the value of the field described by the content of thechannelvariable. So that we have a fast way to get the selected event from theEventPicker.

The actual Custom Drawer

The Custom Drawer must have this declaration:

[CustomPropertyDrawer(typeof(EventPicker))]
public class EventPickerDrawer : PropertyDrawer
{

So that unity can assign this drawer to the class that it’s intended to draw. It’s also important to understand that we won’t be writing something from scratch here. We’ll actually be extending aPropertyDrawerclass that already exists in unity. I won’t explain in depth what this class is or does, for that there is the documentation, but please notice we’re going to override two methods:OnGUIandGetPropertyHeight.

Let’s begin with the easy part:

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return base.GetPropertyHeight(property, label) * 3f + 5f;
    }

Now, what we’re doing here is basically telling unity to reserve a height equal to three times a normal variable plus an extra 5 pixels. This is because we’re going to put one header and then two dropdowns, and each one is going to use the height of one variable.

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.PrefixLabel(position, label);
        Rect tempPos = position;
        tempPos.height = ROWHEIGHT;
        tempPos.y += ROWHEIGHT;
        tempPos.x += position.width * .4f;
        tempPos.width *= .6f;

TheOnGUIfunction actually tells unity what to draw in the inspector when it encounters anEventPickervariable. Here we start by telling it to draw a label which indicates the kind of objetc to which the data belongs and then declaring aRectwith the appropriate size and coordinates to store the rest of the UI elements.

        SerializedProperty channel = property.FindPropertyRelative(EventPicker.channelVarName);
        EditorGUI.PropertyField(tempPos, channel, GUIContent.none);

This bit of code recovers the data about thechannelvariable in theEventPickervia the serialization information (which means you can’t use this on anything that can’t be serialized), then draws new field with that information. Thechannelvariable is identified by its name, that we recover with the static string insideEventPicker.

        tempPos.y += ROWHEIGHT;
        eventChannels chosen = (eventChannels)channel.enumValueIndex;
        SerializedProperty selection = property.FindPropertyRelative(chosen.ToString());
        EditorGUI.PropertyField(tempPos, selection, GUIContent.none);

Here we increment the starting point of theRectso that Unity won’t draw the fields one over the other.

Then we proceed to get the value of the selected channel converted in itsenumform, so that we can then recover the serialization information for the appropriate variable by using theToStringmethod and benefitting from the strong naming convention used in theEventPickerclass.

That’s all folks

So, let’s recap: first you need to create the data container, but in a way that allows you to access all the names of the fields, then we can write another class that must be linked through aCustomPropertyDrawertag to the data class and that overrides thePropertyDrawerfunctions to draw our class the way we want it to.

Now you have a nice UI element that you can use to pick events for your Event System, without having to write it as a horrible string and then converting it. As usual down here there’s all the code in a copy-paste-friendly format. More on this event system is still coming next week. If you want to be sure not losing it, subscribe to my newsletter. For any feedback comments are below or you can just add me on twitter.

using UnityEngine;

[System.Serializable]
public class EventPicker
{
    public const string channelVarName = "channel";
    public eventChannels channel;

    public inGameChannelEvents inGame;
    public menuChannelEvents menu;

    public System.Enum Selected
    {
        get
        {
            switch (channel)
            {
                case eventChannels.menu: return menu;
                case eventChannels.inGame: return inGame;
                default:
                    Debug.LogError("EventPicker was passed an unimplemented channel!!! " + channel.ToString());
                    break;
            }
            return channel;
        }
    }
}

 

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(EventPicker))]
public class EventPickerDrawer : PropertyDrawer
{
    protected virtual float ROWHEIGHT
    {
        get
        { return 18f; }
    }
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.PrefixLabel(position, label);
        Rect tempPos = position;
        tempPos.height = ROWHEIGHT;
        tempPos.y += ROWHEIGHT;
        tempPos.x += position.width * .4f;
        tempPos.width *= .6f;

        SerializedProperty channel = property.FindPropertyRelative(EventPicker.channelVarName);
        EditorGUI.PropertyField(tempPos, channel, GUIContent.none);
        tempPos.y += ROWHEIGHT;

        eventChannels chosen = (eventChannels)channel.enumValueIndex;
        SerializedProperty selection = property.FindPropertyRelative(chosen.ToString());
        EditorGUI.PropertyField(tempPos, selection, GUIContent.none);

    }
    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return base.GetPropertyHeight(property, label) * 3f + 5f;
    }
}

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •