Unity basic custom popup Property drawer

With Unity4 came a nice feature : Property drawers.

They allow you to customize the appearance and behavior of your class property and thus create a nice and robust interface in Unity's editor.

One thing I needed when coding my classes was the ability to show a basic popup select list. It allowed me to choose the right behavior for the gameObject depending on this particular field value.

This is an important feature because Unity layers and tags are very limited and you can assign only one tag and one layer to a gameObject (and layers are limited in numbers too). If I want to differenciate two gameObjects with the same script attached to it I need to expose a string in the editor and choose the right behavior depending on what is written in the text box of the string. Thus I will be able do determine in the code if the gameObject is a "box" or a "door" hideout for example.

I can do this very easily by setting the attribute to public. Later in my code I will choose to hide behind a door or behind a box.

    
 public string hideoutType = "Box"; /*!< Hideout type. Different behaviors. */
 

But if I work with other people, I will have to tell them that they should write "Box" or "Door" in the editor box, and you can see that this method is not very robust.
It would be great if I could show a popup with a select list with "Box" or "Door". So let's do this!

Unity property drawer popup example

To achieve this new mission we need 3 classes.

  • Our class that will use the exposed attribute
  • A property Attribute class, that will stores an enriched version of the string
  • A property Drawer class, that will draw the gui and use the property Attribute

Our class

We need to add a new line before our exposed attribute with the name of the attribute (I chose "PopupAttribute") followed by arguments between "( )" separated with "," and all between "[ ]". We will use the arguments to create the list. I chose to use the first argument as the label of the popup.

     
    [PopupAttribute ("Type", "Box", "Door")] /*!< Custom Property Drawer */
    public string hideoutType = "Box"; /*!< Hideout type. Different behaviors. */
 

Now we need to add all these new information to the string attribute. Let's create the popup Property attribute class.

Property Attribute

Create a new folder called "Attributes" in your project and create a new script that inherits from "PropertyAttribute" class. Call it "PopupAttribute". This class will contains all the information needed for the popup to be drawn. It is like a enriched version of the simple string attribute with the information we added into brackets (ie the label and the options of the popup).

So we only need to create the options variable and the label variable and assign them in the class constructor. Since I chose to use the first argument as the label I used a temporary queue to filter out the label before populating the options.

The PopupAttribute options and label are in readonly because we do not want to modify them, but we still want to access them from the Property drawer later.

Note: I added System.Collections.Generic to use the Queue.

  
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

//! PopupAttribute
/*!
 * Store the options for the popup.
 * Store the label of the popup field.
 */
public class PopupAttribute : PropertyAttribute
{
    public readonly string[] options; /*!< Popup options. */
    public readonly string label; /*!< Popup label. */
    
    private Queue<string> choices = new Queue<string>(); /*!< Temporary queue used to filter out the label. */

    // Constructor
    public PopupAttribute (params string[] attrs)
    {
        // Use a queue to filter out
        // the label
        foreach (string attr in attrs)
        {
            choices.Enqueue (attr);
        }

        // Extract label
        label = choices.Dequeue ();

        // Populate options
        options = choices.ToArray();
    }
}
 

Now we have everything we need to draw the popup (the options and the label). Let's create the Property drawer class.

Property drawer

Create a new folder called "Editor" in your project and create a new script that inherits from "PropertyDrawer" class. Call it "PopupPropertyDrawer". Add "using UnityEditor;" to make Unity know that this an editor script.
Now we need to tell Unity that this particular custom Property drawer will be used for our custom popup Property Attribute. And this is done by adding this line before our class:

    
[CustomPropertyDrawer(typeof(PopupAttribute))]  
 

Ok time to retrieve our super class PopupAttribute label and options with the PropertyDrawer attribute property. To make it easier let's create a property of the class that returns the attribute casted with our class type, so we won't have to write each time we need it.

 
    private PopupAttribute popupAttr /*!< Access the property easily. */
    {
        get { return ((PopupAttribute)attribute); }
    }

Now the only thing we need to do is to draw a GUI popup with the correct options and stores the result in our property. So let's create a GUI with a new OnGUI method of the PropertyDrawer class and do not forget to add the "override" keyword.

  
    // Draw the property in a popup
    public override void OnGUI (Rect position, SerializedProperty property, GUIContent label)
    {}
 

And now we have access to the property itself (the string "hideoutType" we created in our class) with "SerializedProperty property".

The last thing we need to do is to find the right index of the select list before drawing the popup, and then we can save it back into the property. For this I am using Array.FindIndex() function and output an error message if we could not find the string in the options. If we find it then we can draw the popup and save the new index.

Note: I added "using System;" line at the top of the script to use the Array functions.

  
    // Draw the property in a popup
    public override void OnGUI (Rect position, SerializedProperty property, GUIContent label)
    {
        // Retrieve current string
        string current = property.stringValue;

        // Find index of the string
        int stringIndex = Array.FindIndex(popupAttr.options, x => x.Contains(current));

        // Force index to zero if we could not find the current string in the options
        if (stringIndex == -1)
        {
            stringIndex = 0;
            Debug.LogError("[PopupPropertyDrawer] Warning : Could not find the property string " + property.stringValue + " in the options.");
        }

        // Draw the popup
        int index = EditorGUI.Popup(position,
                                    popupAttr.label,
                                    stringIndex,
                                    popupAttr.options);

        // Save the new index in the attribute
        property.stringValue = popupAttr.options[index];
    }
  

The full code of the custom Property drawer (link on GitHub):

  
using UnityEngine;
using UnityEditor; // Editor directory
using System.Collections;
using System; // Array

//! PopupPropertyDrawer
/*!
 * Custom property drawer for PopupAttribute.
 * Expose a string popup choice in the editor.
 */
[CustomPropertyDrawer(typeof(PopupAttribute))]
public class PopupPropertyDrawer : PropertyDrawer {
    
    private PopupAttribute popupAttr /*!< Access the property easily. */
    {
        get { return ((PopupAttribute)attribute); }
    }

    // Draw the property in a popup
    public override void OnGUI (Rect position, SerializedProperty property, GUIContent label)
    {
        // Retrieve current string
        string current = property.stringValue;

        // Find index of the string
        int stringIndex = Array.FindIndex(popupAttr.options, x => x.Contains(current));

        // Force index to zero if we could not find the current string in the options
        if (stringIndex == -1)
        {
            stringIndex = 0;
            Debug.LogError("[PopupPropertyDrawer] Warning : Could not find the property string " + property.stringValue + " in the options.");
        }

        // Draw the popup
        int index = EditorGUI.Popup(position,
                                       popupAttr.label,
                                    stringIndex,
                                    popupAttr.options);

        // Save the new index in the attribute
        property.stringValue = popupAttr.options[index];
    }
}
  

This was a very simple example of a custom popup Property drawer. I hope you enjoyed it.

GitHub mark Download it on GitHub.

Menu