Making only one entry of a Flag Enum selectable in the Unity Editor

1 minute read

Another small trick, easily reusable. Attentive users might have noticed something odd in my recent blog post about Scene Understanding. You can assign a Surface Type to a Layer. But a the SpatialAwarenessSurfaceTypes has the [Flags] attribute - so this is a bit mask, and by default Unity draws an editor for a bit mask like this:

This is not what I wanted - I did not want the developer to select more than one layer. So I used a little trick that allows the user to select only one entry from the bit mask:

To make this possible, the field that’s used to store the surface type in has a special attribute

[Serializable]
public class SpatialUnderstandingLayer
{
    [SerializeField,SingleEnumFlagSelect(EnumType = typeof(SpatialAwarenessSurfaceTypes))]
    [Tooltip("The surface type to act on")]
    private SpatialAwarenessSurfaceTypes surfaceType

This attribute, SingleEnumFlagSelect, is defined like this:

public class SingleEnumFlagSelectAttribute : PropertyAttribute
{
    private Type enumType;

    public Type EnumType
    {
        get => enumType;
        set
        {
            if (value == null)
            {
                Debug.LogError($"{GetType().Name}: EnumType cannot be null");
                return;
            }
            if (!value.IsEnum)
            {
                Debug.LogError($"{GetType().Name}: EnumType is {value.Name} this is not an enum");
                return;
            }
            enumType = value;
            IsValid = true;
        }
    }
    
    public bool IsValid { get; private set; }
}

This accepts an Enum and Enum only, as you can see. And this attribute triggers this little editor script:

[CustomPropertyDrawer(typeof(SingleEnumFlagSelectAttribute))]
public class SingleEnumFlagSelectAttributeEditor : PropertyDrawer
{
    public override void OnGUI(Rect position, 
      SerializedProperty property, GUIContent label)
    {
        var singleEnumFlagSelectAttribute = 
          (SingleEnumFlagSelectAttribute)attribute;
        if (!singleEnumFlagSelectAttribute.IsValid)
        {
            return;
        }
        var displayTexts = new List<GUIContent>();
        var enumValues = new List<int>();
        foreach (var displayText in 
          Enum.GetValues(singleEnumFlagSelectAttribute.EnumType))
        {
            displayTexts.Add(new GUIContent(displayText.ToString()));
            enumValues.Add((int)displayText);
        }

        property.intValue = EditorGUI.IntPopup(position, label, property.intValue,
            displayTexts.ToArray(), enumValues.ToArray());
    }
}

This replaces the default editor behavior by a single select popup with the display names of the bit masked Enum as texts, and their corresponding int value as value. Net result: you can only pick one value, exactly as I wanted.

I kind of nicked the idea from the PhysicsLayerAttributeDrawer class in the MRTK that does the same for layers, but for layers only. This is basically generically applicable to every flagged Enum

The code is in the Scene Understanding project from two blog posts ago.