Custom inspector multi-select enum dropdown?

I want to make a drop-down in a custom inspector that allows me to select multiple enums from a popup, exactly like the popups you get when selecting a camera’s culling mask, a projector’s “ignore” layers, or a layermask. (see picture)

Am I missing a simple way to do this? What would the returned result of a change be? A List or array of FooEnum elements? I can jury-rig an equivalent alternative, but since this GUI element does exactly what I want, I’d love to use one if I could find out how!

7528-flags.jpg

I’ve written a “wrapper” function, mainly for a general PropertyDrawer, which actual uses the enum values. However, this changes the Nothing and Everything behaviour, but all enum values work fine, even combined values.

// Have to be defined somewhere in a runtime script file
// BitMaskAttribute.cs
public class BitMaskAttribute : PropertyAttribute
{
    public System.Type propType;
    public BitMaskAttribute(System.Type aType)
    {
        propType = aType;
    }
}

This should go into an editor script:

// /editor/EnumBitMaskPropertyDrawer.cs
using UnityEngine;
using UnityEditor;
using System.Collections;

public static class EditorExtension
{
    public static int DrawBitMaskField (Rect aPosition, int aMask, System.Type aType, GUIContent aLabel)
    {
        var itemNames = System.Enum.GetNames(aType);
        var itemValues = System.Enum.GetValues(aType) as int[];
        
        int val = aMask;
        int maskVal = 0;
        for(int i = 0; i < itemValues.Length; i++)
        {
            if (itemValues[i] != 0)
            {
                if ((val & itemValues[i]) == itemValues[i])
                    maskVal |= 1 << i;
            }
            else if (val == 0)
                maskVal |= 1 << i;
        }
        int newMaskVal = EditorGUI.MaskField(aPosition, aLabel, maskVal, itemNames);
        int changes = maskVal ^ newMaskVal;
 
        for(int i = 0; i < itemValues.Length; i++)
        {
            if ((changes & (1 << i)) != 0)            // has this list item changed?
            {
                if ((newMaskVal & (1 << i)) != 0)     // has it been set?
                {
                    if (itemValues[i] == 0)           // special case: if "0" is set, just set the val to 0
                    {
                        val = 0;
                        break;
                    }
                    else
                        val |= itemValues[i];
                }
                else                                  // it has been reset
                {
                    val &= ~itemValues[i];
                }
            }
        }
        return val;
    }
}

[CustomPropertyDrawer(typeof(BitMaskAttribute))]
public class EnumBitMaskPropertyDrawer : PropertyDrawer
{
    public override void OnGUI (Rect position, SerializedProperty prop, GUIContent label)
    {
        var typeAttr = attribute as BitMaskAttribute;
        // Add the actual int value behind the field name
        label.text = label.text + "("+prop.intValue+")";
        prop.intValue = EditorExtension.DrawBitMaskField(position, prop.intValue, typeAttr.propType, label);
    }
}

Now just imagine this example:

public enum EMyEnum
{
    None         = 0x00,
    Cow          = 0x01,
    Chicken      = 0x02,
    Cat          = 0x04,
    Dog          = 0x08,
    CowChicken   = 0x03,
    CatDog       = 0x0C,
    All          = 0x0F,
}
 
public class Example : MonoBehaviour
{
    [BitMask(typeof(EMyEnum))]
    public EMyEnum someMask;
}

This will automatically draw my modified enum-mask field.

ps: I just fixed the completely broken code formatting due to the Unity Answers migration. I haven’t had the time to test if the code actually works. There were a lot of messed up things (especially [i] being replaced with italics tags).

You need to use EnumMaskField and make sure your enum is decorated with the [Flags] attribute.

@AlwaysSunny @Bunny83 @whydoidoit @Aqibsadiq :

A clean solution for current versions of Unity.

EnumFlagAttributePropertyDrawer.cs

using System;
using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(EnumFlagAttribute))]
class EnumFlagAttributePropertyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        label = EditorGUI.BeginProperty(position, label, property);

        var oldValue = (Enum)fieldInfo.GetValue(property.serializedObject.targetObject);
        var newValue = EditorGUI.EnumFlagsField(position, label, oldValue);
        if (!newValue.Equals(oldValue))
        {
            property.intValue = (int)Convert.ChangeType(newValue, fieldInfo.FieldType);
        }

        EditorGUI.EndProperty();
    }
}

EnumFlagAttribute.cs

using UnityEngine;

/// <summary>
/// Display multi-select popup for Flags enum correctly.
/// </summary>
public class EnumFlagAttribute : PropertyAttribute
{
}

Usage example:

public class MyFlagHolder : MonoBehaviour
{
    [SerializeField, EnumFlag]
    private MyFlag flags;
}

[Flags]
public enum MyFlag
{
    Flag1 = 1,
    Flag2 = 2,
    Flag3 = 4,
    Flag4 = 8
}

@Bunny83 @AlwaysSunny @whydoidoit

Here is a simpler way

Simple 4 Steps

Step 1 : Make a new Script “EnumFlagsAttribute”

using UnityEngine;
using System.Collections;
public class EnumFlagsAttribute : PropertyAttribute
 {
     public EnumFlagsAttribute() { }
 }

Step 2 : Make another Script “EnumFlagsAttributeDrawer”

using UnityEngine;
using System.Collections;
using System;
using UnityEditor;

[CustomPropertyDrawer(typeof(EnumFlagsAttribute))]
 public class EnumFlagsAttributeDrawer : PropertyDrawer
 {
     public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _label)
     {
         _property.intValue = EditorGUI.MaskField( _position, _label, _property.intValue, _property.enumNames );
     }
 }

Step 3: Enum Declaration

[System.Flags]
	public enum FurnitureType
	{
		None , SofaPrefab , Curtains , Table , Chairs 
        }

 [EnumFlagsAttribute]
	public  FurnitureType   enumType;

Step 4: Getting Selected Elements

List<int> ReturnSelectedElements()
	{

		List<int> selectedElements = new List<int>();
		for (int i = 0; i < System.Enum.GetValues(typeof(FurnitureType)).Length; i++)
		{
		    int layer = 1 << i;
			if (((int) enumType & layer) != 0)
		    {
		    	selectedElements.Add(i);
		    }
		}

		return selectedElements;

	}

Instead of Add(i) it should be:

selectedElements.Add(
    (int) Enum.GetValues( typeof(FurnitureType) ).GetValue(i)
);

can confirm aqibsadiqs solution still works. Im using it as a tag mask to destroy objects except for those chosen in the enum list, so I tweaked it to return string instead


void OnCollisionEnter2D(Collision2D collision)
{
    selectedObjects = ReturnSelectedElements();
    if (!selectedObjects.Contains(collision.collider.tag))
    {
        //action
    }
}


List<string> ReturnSelectedElements()
{

    List<string> selectedElements = new List<string>();
    for (int i = 0; i < System.Enum.GetValues(typeof(DontDestroyOn)).Length; i++)
    {
        int layer = 1 << i;
        if (((int)dontDestroyOn & layer) != 0)
        {
            selectedElements.Add(Enum.GetValues(typeof(DontDestroyOn)).GetValue(i).ToString());
        }
    }

    return selectedElements;

}
public enum DontDestroyOn
{
    Player,
    Enemy,
    Hazard,
    Ground,
    LOS
}

Id prefer to have it in the start function instead of making a list on every collision, but for whatever reason its not working there