Search Unity

  1. Improved Prefab workflow (includes Nested Prefabs!), 2D isometric Tilemap and more! Get the 2018.3 Beta now.
    Dismiss Notice
  2. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  3. Let us know a bit about your interests, and if you'd like to become more directly involved. Take our survey!
    Dismiss Notice
  4. Improve your Unity skills with a certified instructor in a private, interactive classroom. Watch the overview now.
    Dismiss Notice
  5. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

Property drawer for enum flags/masks (download)

Discussion in 'Extensions & OnGUI' started by ItsaMeTuni, Feb 15, 2018.

  1. ItsaMeTuni

    ItsaMeTuni

    Joined:
    Jan 19, 2015
    Posts:
    40
    Hi guys, I'd like to share with you a little script I made that adds an attribute called EnumMask that you can add to an enum field. Then that enum field will be displayed differently in the editor (see the screenshot) that makes it way easier to edit than a regular mask field.


    Example:
    Code (CSharp):
    1. [System.Flags]
    2. public enum BlockSides
    3. {
    4.     Left    = (1 << 0),
    5.     Right   = (1 << 1),
    6.     Front   = (1 << 2),
    7.     Back    = (1 << 3),
    8.     Up      = (1 << 4),
    9.     Down    = (1 << 5)
    10. }
    11.  
    12. [EnumMask] BlockSides attachableSides;
    13.  
    All you've got to do is copy the source code from here and add it to a C# source file (outside of the Editor folder). Or, if you prefer, download the .cs file I attached to this post and add it to your project.

    EDIT: As pointed out by madgvox here this property drawer did not support "holes" in the flags values (check out the link so you understand what I mean) and I also found out it didn't support types such as long, ulong, short, ushort, byte and sbyte. All of these issues have been fixed and both the source code and the file for download have been updated.

    Code (CSharp):
    1.  
    2. /*
    3.  Written by: Lucas Antunes (aka ItsaMeTuni), lucasba8@gmail.com
    4.  In: 2/15/2018
    5.  The only thing that you cannot do with this script is sell it by itself without substantially modifying it.
    6.  */
    7.  
    8. using System;
    9. using UnityEngine;
    10.  
    11. #if UNITY_EDITOR
    12. using UnityEditor;
    13. [CustomPropertyDrawer(typeof(EnumMaskAttribute))]
    14. public class EnumMaskPropertyDrawer : PropertyDrawer
    15. {
    16.     bool foldoutOpen = false;
    17.  
    18.     object theEnum;
    19.     Array enumValues;
    20.     Type enumUnderlyingType;
    21.  
    22.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    23.     {
    24.         if (foldoutOpen)
    25.             return EditorGUIUtility.singleLineHeight * (Enum.GetValues(fieldInfo.FieldType).Length + 2);
    26.         else
    27.             return EditorGUIUtility.singleLineHeight;
    28.     }
    29.  
    30.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    31.     {
    32.         theEnum = fieldInfo.GetValue(property.serializedObject.targetObject);
    33.         enumValues = Enum.GetValues(theEnum.GetType());
    34.         enumUnderlyingType = Enum.GetUnderlyingType(theEnum.GetType());
    35.  
    36.         //We need to convert the enum to its underlying type, if we don't it will be boxed
    37.         //into an object later and then we would need to unbox it like (UnderlyingType)(EnumType)theEnum.
    38.         //If we do this here we can just do (UnderlyingType)theEnum later (plus we can visualize the value of theEnum in VS when debugging)
    39.         theEnum = Convert.ChangeType(theEnum, enumUnderlyingType);
    40.  
    41.         EditorGUI.BeginProperty(position, label, property);
    42.  
    43.         foldoutOpen = EditorGUI.Foldout(new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight), foldoutOpen, label);
    44.  
    45.         if (foldoutOpen)
    46.         {
    47.             //Draw the All button
    48.             if (GUI.Button(new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight * 1, 30, 15), "All"))
    49.             {
    50.                 for (int i = 0; i < Enum.GetNames(fieldInfo.FieldType).Length; i++)
    51.                 {
    52.                     ToggleIndex(i, true);
    53.                 }
    54.             }
    55.  
    56.             //Draw the None button
    57.             if (GUI.Button(new Rect(position.x + 32, position.y + EditorGUIUtility.singleLineHeight * 1, 40, 15), "None"))
    58.             {
    59.                 for (int i = 0; i < Enum.GetNames(fieldInfo.FieldType).Length; i++)
    60.                 {
    61.                     ToggleIndex(i, false);
    62.                 }
    63.             }
    64.  
    65.             //Draw the list
    66.             for (int i = 0; i < Enum.GetNames(fieldInfo.FieldType).Length; i++)
    67.             {
    68.                 if (EditorGUI.Toggle(new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight * (2 + i), position.width, EditorGUIUtility.singleLineHeight), Enum.GetNames(fieldInfo.FieldType)[i], IsSet(i)))
    69.                 {
    70.                     ToggleIndex(i, true);
    71.                 }
    72.                 else
    73.                 {
    74.                     ToggleIndex(i, false);
    75.                 }
    76.             }
    77.         }
    78.  
    79.         fieldInfo.SetValue(property.serializedObject.targetObject, theEnum);
    80.         property.serializedObject.ApplyModifiedProperties();
    81.     }
    82.  
    83.     /// <summary>
    84.     /// Get the value of an enum element at the specified index (i.e. at the index of the name of the element in the names array)
    85.     /// </summary>
    86.     object GetEnumValue(int _index)
    87.     {
    88.         return Convert.ChangeType(enumValues.GetValue(_index), enumUnderlyingType);
    89.     }
    90.  
    91.     /// <summary>
    92.     /// Sets or unsets a bit in theEnum based on the index of the enum element (i.e. the index of the element in the names array)
    93.     /// </summary>
    94.     /// <param name="_set">If true the flag will be set, if false the flag will be unset.</param>
    95.     void ToggleIndex(int _index, bool _set)
    96.     {
    97.         if(_set)
    98.         {
    99.             //enum = enum | val
    100.             theEnum = DoOrOperator(theEnum, GetEnumValue(_index), enumUnderlyingType);
    101.         }
    102.         else
    103.         {
    104.             object val = GetEnumValue(_index);
    105.             object notVal = DoNotOperator(val, enumUnderlyingType);
    106.  
    107.             //enum = enum & ~val
    108.             theEnum = DoAndOperator(theEnum, notVal, enumUnderlyingType);
    109.         }
    110.     }
    111.  
    112.     /// <summary>
    113.     /// Checks if a bit flag is set at the provided index of the enum element (i.e. the index of the element in the names array)
    114.     /// </summary>
    115.     bool IsSet(int _index)
    116.     {
    117.         object val = DoAndOperator(theEnum, GetEnumValue(_index), enumUnderlyingType);
    118.  
    119.         if (enumUnderlyingType == typeof(int))
    120.         {
    121.             return (int)val != 0;
    122.         }
    123.         else if (enumUnderlyingType == typeof(uint))
    124.         {
    125.             return (uint)val != 0;
    126.         }
    127.         else if (enumUnderlyingType == typeof(short))
    128.         {
    129.             return (short)val != 0;
    130.         }
    131.         else if (enumUnderlyingType == typeof(ushort))
    132.         {
    133.             return (ushort)val != 0;
    134.         }
    135.         else if (enumUnderlyingType == typeof(long))
    136.         {
    137.             return (long)val != 0;
    138.         }
    139.         else if (enumUnderlyingType == typeof(ulong))
    140.         {
    141.             return (ulong)val != 0;
    142.         }
    143.         else if (enumUnderlyingType == typeof(byte))
    144.         {
    145.             return (byte)val != 0;
    146.         }
    147.         else if (enumUnderlyingType == typeof(sbyte))
    148.         {
    149.             return (sbyte)val != 0;
    150.         }
    151.         else
    152.         {
    153.             throw new System.ArgumentException("Type " + enumUnderlyingType.GetType().FullName + " not supported.");
    154.         }
    155.     }
    156.  
    157.     /// <summary>
    158.     /// Call the bitwise OR operator (|) on _lhs and _rhs given their types.
    159.     /// Will basically return _lhs | _rhs
    160.     /// </summary>
    161.     /// <param name="_lhs">Left-hand side of the operation.</param>
    162.     /// <param name="_rhs">Right-hand side of the operation.</param>
    163.     /// <param name="_type">Type of the objects.</param>
    164.     /// <returns>Result of the operation</returns>
    165.     static object DoOrOperator(object _lhs, object _rhs, Type _type)
    166.     {
    167.         if(_type == typeof(int))
    168.         {
    169.             return ((int)_lhs) | ((int)_rhs);
    170.         }
    171.         else if (_type == typeof(uint))
    172.         {
    173.             return ((uint)_lhs) | ((uint)_rhs);
    174.         }
    175.         else if (_type == typeof(short))
    176.         {
    177.             //ushort and short don't have bitwise operators, it is automatically converted to an int, so we convert it back
    178.             return unchecked((short)((short)_lhs | (short)_rhs));
    179.         }
    180.         else if (_type == typeof(ushort))
    181.         {
    182.             //ushort and short don't have bitwise operators, it is automatically converted to an int, so we convert it back
    183.             return unchecked((ushort)((ushort)_lhs | (ushort)_rhs));
    184.         }
    185.         else if (_type == typeof(long))
    186.         {
    187.             return ((long)_lhs) | ((long)_rhs);
    188.         }
    189.         else if (_type == typeof(ulong))
    190.         {
    191.             return ((ulong)_lhs) | ((ulong)_rhs);
    192.         }
    193.         else if (_type == typeof(byte))
    194.         {
    195.             //byte and sbyte don't have bitwise operators, it is automatically converted to an int, so we convert it back
    196.             return unchecked((byte)((byte)_lhs | (byte)_rhs));
    197.         }
    198.         else if (_type == typeof(sbyte))
    199.         {
    200.             //byte and sbyte don't have bitwise operators, it is automatically converted to an int, so we convert it back
    201.             return unchecked((sbyte)((sbyte)_lhs | (sbyte)_rhs));
    202.         }
    203.         else
    204.         {
    205.             throw new System.ArgumentException("Type " + _type.FullName + " not supported.");
    206.         }
    207.     }
    208.  
    209.     /// <summary>
    210.     /// Call the bitwise AND operator (&) on _lhs and _rhs given their types.
    211.     /// Will basically return _lhs & _rhs
    212.     /// </summary>
    213.     /// <param name="_lhs">Left-hand side of the operation.</param>
    214.     /// <param name="_rhs">Right-hand side of the operation.</param>
    215.     /// <param name="_type">Type of the objects.</param>
    216.     /// <returns>Result of the operation</returns>
    217.     static object DoAndOperator(object _lhs, object _rhs, Type _type)
    218.     {
    219.         if (_type == typeof(int))
    220.         {
    221.             return ((int)_lhs) & ((int)_rhs);
    222.         }
    223.         else if (_type == typeof(uint))
    224.         {
    225.             return ((uint)_lhs) & ((uint)_rhs);
    226.         }
    227.         else if (_type == typeof(short))
    228.         {
    229.             //ushort and short don't have bitwise operators, it is automatically converted to an int, so we convert it back
    230.             return unchecked((short)((short)_lhs & (short)_rhs));
    231.         }
    232.         else if (_type == typeof(ushort))
    233.         {
    234.             //ushort and short don't have bitwise operators, it is automatically converted to an int, so we convert it back
    235.             return unchecked((ushort)((ushort)_lhs & (ushort)_rhs));
    236.         }
    237.         else if (_type == typeof(long))
    238.         {
    239.             return ((long)_lhs) & ((long)_rhs);
    240.         }
    241.         else if (_type == typeof(ulong))
    242.         {
    243.             return ((ulong)_lhs) & ((ulong)_rhs);
    244.         }
    245.         else if (_type == typeof(byte))
    246.         {
    247.             return unchecked((byte)((short)_lhs & (short)_rhs));
    248.         }
    249.         else if (_type == typeof(sbyte))
    250.         {
    251.             //byte and sbyte don't have bitwise operators, it is automatically converted to an int, so we convert it back
    252.             return unchecked((sbyte)((sbyte)_lhs & (sbyte)_rhs));
    253.         }
    254.         else
    255.         {
    256.             throw new System.ArgumentException("Type " + _type.FullName + " not supported.");
    257.         }
    258.     }
    259.  
    260.     /// <summary>
    261.     /// Call the bitwise NOT operator (~) on _lhs given its type.
    262.     /// Will basically return ~_lhs
    263.     /// </summary>
    264.     /// <param name="_lhs">Left-hand side of the operation.</param>
    265.     /// <param name="_type">Type of the object.</param>
    266.     /// <returns>Result of the operation</returns>
    267.     static object DoNotOperator(object _lhs, Type _type)
    268.     {
    269.         if (_type == typeof(int))
    270.         {
    271.             return ~(int)_lhs;
    272.         }
    273.         else if (_type == typeof(uint))
    274.         {
    275.             return ~(uint)_lhs;
    276.         }
    277.         else if (_type == typeof(short))
    278.         {
    279.             //ushort and short don't have bitwise operators, it is automatically converted to an int, so we convert it back
    280.             return unchecked((short)~(short)_lhs);
    281.         }
    282.         else if (_type == typeof(ushort))
    283.         {
    284.  
    285.             //ushort and short don't have bitwise operators, it is automatically converted to an int, so we convert it back
    286.             return unchecked((ushort)~(ushort)_lhs);
    287.         }
    288.         else if (_type == typeof(long))
    289.         {
    290.             return ~(long)_lhs;
    291.         }
    292.         else if (_type == typeof(ulong))
    293.         {
    294.             return ~(ulong)_lhs;
    295.         }
    296.         else if (_type == typeof(byte))
    297.         {
    298.             //byte and sbyte don't have bitwise operators, it is automatically converted to an int, so we convert it back
    299.             return (byte)~(byte)_lhs;
    300.         }
    301.         else if (_type == typeof(sbyte))
    302.         {
    303.             //byte and sbyte don't have bitwise operators, it is automatically converted to an int, so we convert it back
    304.             return unchecked((sbyte)~(sbyte)_lhs);
    305.         }
    306.         else
    307.         {
    308.             throw new System.ArgumentException("Type " + _type.FullName + " not supported.");
    309.         }
    310.     }
    311. }
    312. #endif
    313.  
    314. [System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false)]
    315. public class EnumMaskAttribute : PropertyAttribute
    316. {
    317.  
    318. }
    319.  
    320.  

    I hope this helps someone!
     

    Attached Files:

    Last edited: Sep 28, 2018
    mitaywalle, jheiling, zyzyx and 3 others like this.
  2. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    133
    Alternatively you can use the MaskField to display like a multi select dropdown

    Code (csharp):
    1.  
    2.     public override void OnGUI(Rect position,
    3.                                 SerializedProperty property,
    4.                                 GUIContent label)
    5.     {
    6.         EditorGUI.BeginChangeCheck();
    7.         uint a = (uint)(EditorGUI.MaskField(position, label, property.intValue, property.enumNames));
    8.         if (EditorGUI.EndChangeCheck())
    9.         {
    10.             property.intValue = (int)a;
    11.         }
    12.     }
    13. [code]
     
  3. ItsaMeTuni

    ItsaMeTuni

    Joined:
    Jan 19, 2015
    Posts:
    40
    Yes, but mask fields suck (a lot). You can't see what the value is until you click it so it shows the popup and if you need to set a lot of items in it it's going to take you a lot of time.
     
  4. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    133
    Yeah, I agree that the "mixed..." isn't very nice. Its extra annoying because layermask types work nicely. i.e. instead of mixed, it will say: layer1, layer2, layer3

    but fair enough on your other points
     
  5. SirChick

    SirChick

    Joined:
    Feb 27, 2013
    Posts:
    317
    Does this script go in the editor folder? Doesn't seem to work for me i can't use the attribute it doesn't appear to exist.
     
  6. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    450
    Unfortunately it seems that this script suffers from the same core flaw that Unity's own FlagsField does.

    By iterating on the names only, and using the index as the enum value, you make it impossible to use enums that have gaps in their values (a situation that is very possible to occur):

    Code (CSharp):
    1. [Flags]
    2. enum MyEnum {
    3.   Foo = 0x1,
    4.   Bar = 0x2,
    5.  
    6.   Baz = 0x8 // <-- skips 0x4
    7. }
    You should be iterating over Enum.GetValues instead of creating your own.
     
    ItsaMeTuni likes this.
  7. ItsaMeTuni

    ItsaMeTuni

    Joined:
    Jan 19, 2015
    Posts:
    40
    I haven't thought about this at the time I wrote this script, neither I thought that the user could want to use a ushort, a long, a ulong, etc instead of an int as the enum subtype.

    Just to let you know I am working on solving this. Solving skipped bits was easy, the thing is that I encountered a very odd error that for some reason ~(ushort)1 is showing up in VS as -2 (which shouldn't even be possible) and throwing an exception because of that...
     
    Madgvox likes this.
  8. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    450
    ~1
    is two's complement
    -2
    :

    Code (Raw):
    1. 11111111 11111110
    My first guess is that something's converting the ushort back to a signed value somewhere.
     
  9. ItsaMeTuni

    ItsaMeTuni

    Joined:
    Jan 19, 2015
    Posts:
    40
    I asked a question in StackOverflow about this here and it turns out that short and ushort don't have bitwise operators so they are converted to int or uint before the operation. I won't be finishing the fix now as I'm busy at the moment.
     
  10. ItsaMeTuni

    ItsaMeTuni

    Joined:
    Jan 19, 2015
    Posts:
    40
    @Madgvox I fixed it and updated the post with the working source and file for download :)
    I had to make some wizardry to make it work XD but I really appreciate that you replied to this thread since fixing this was fun and made me learn a bit more about C#.
     
    Madgvox likes this.
  11. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    450
    Sure, no problem!