Multiple Enum Inspector

Hello, I needed a way to select and store multiple Enums. I found this on the forum.

Attribute

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

public class EnumFlagsAttribute : PropertyAttribute
{
    public EnumFlagsAttribute() { }

    public static List<int> GetSelectedIndexes<T>(T val) where T : IConvertible
    {
        List<int> selectedIndexes = new List<int>();
        for (int i = 0; i < System.Enum.GetValues(typeof(T)).Length; i++)
        {
            int layer = 1 << i;
            if ((Convert.ToInt32(val) & layer) != 0)
            {
                selectedIndexes.Add(i);
            }
        }
        return selectedIndexes;
    }
    public static List<string> GetSelectedStrings<T>(T val) where T : IConvertible
    {
        List<string> selectedStrings = new List<string>();
        for (int i = 0; i < Enum.GetValues(typeof(T)).Length; i++)
        {
            int layer = 1 << i;
            if ((Convert.ToInt32(val) & layer) != 0)
            {
                selectedStrings.Add(Enum.GetValues(typeof(T)).GetValue(i).ToString());
            }
        }
        return selectedStrings;
    }

    public static bool IsSelected<T>(T val, int index) where T : IConvertible
    {
        int layer = 1 << index;
        return ((Convert.ToInt32(val) & layer) != 0);
    }

    public static bool IsSelected<T>(T val, string name) where T : IConvertible
    {
        int index = Enum.GetNames(typeof(T)).ToList().IndexOf(name);
        int layer = 1 << index;
        return ((Convert.ToInt32(val) & layer) != 0);
    }
}

Drawer

#if UNITY_EDITOR
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);
    }
}
#endif

And it works like I wanted it to.
The problem is that when I select multiple Objects with a script that uses this, all of them are assigned the same value. So that all selected values get overwritten.

I tried to fix it, but I couldn’t get it to work properly, maybe someone here knows a fix for it.

Best regards

And what would be a desired behavior?

When multiple objects are selected, you can only do the overwrite or you can prohibit editing altogether. These are your only options really.

1 Like

I don’t want to change the values, If I want to move them to another place, all get set to the same value. I have to select them separately and move them.

Here is a Video of the problem:

https://vimeo.com/824333375

You definitely want to make a provision for the mixed values in your drawer.
Let me check the docs, there is an attribute.

1 Like

It’s because your drawer is just always assigning to the field. You need to grab the value from the field, and only assign it to the serialized property if it changes from what the current value is.

Mind you, you should be using more robust methods for this such as scriptable objects or SerializeReference fields. Enums are a trap.

Also doesn’t Unity already support Enum Flags enums?

2 Likes

With drawers, you check whether a serialized property has multiple values by testing hasMultipleDifferentValues.

2 Likes

This is how I handled this for my case (there’s more in this example than you need)

mixedGUI(_target.hasMultipleDifferentValues, () => {
  var newVal = drawDropDownSelector(rect, clamp(val, _records.Length), usesTooltips);
  if(val != newVal) _target.stringValue = _records[newVal].name;
})

void mixedGUI(bool condition, Action action) {
  var saved = EditorGUI.showMixedValue;
  EditorGUI.showMixedValue = condition;
  action.Invoke();
  EditorGUI.showMixedValue = saved;
}

int drawDropDownSelector(Rect rect, int selected, bool usesTooltips)
  => EditorGUI.Popup(rect, GUIContent.none, selected, _cached??= extractGUIContentColumn(usesTooltips));

You can exactly how this works if you grab the code from here . It’s made for enums specifically.

1 Like

Is this for the flags? Yes Unity already does flag enums, hm.

1 Like

Just found out it does. I had the problem that it was “bugging” but I just did it wrong…
Sorry to all for wasting your time…

All good; we all learnt something here.

Nonetheless using a scriptable object to define a room type will be more extensible in the long run.

2 Likes

Thanks for the fast help and nice responses! :slight_smile:

You can also check out that solution I shared with you above. It’s an enum-like with some nice features: for example, it automatically serializes into strings, but you work with it like it was an enum in code. Pretty much the best of the two worlds if you need a simple flow. The thread is also a full tutorial on how it was built.

This is the link , again for reference. Here you can observe the simplest use case scenario .

1 Like

Thank you! :slight_smile:

1 Like

It doesn’t work with the flags however! It’s intended only as a replacement when you really want like some damage types or monster types or whatever, and you want something simple yet reliable.

The best way would be to provide a text field, and simply type in what you’d like, because well, then you don’t have to worry if you add or remove a type, it’s always reliably saved as a string. Sadly, however, text fields leave a lot to be desired, like you’re free to make spelling errors, you waste time typing, and then you have to compare strings in your code, it’s soo bad on so many fronts.

Well, this was my motivation to make this, it’s basically a type that you can declare like you would declare an enum, then use it in the code exactly the same (including the values if you need them), but you also get the editor behavior for free: the string serialization, the drop down, even a special ‘none’ field if you’re making a selector.

And if you decide against it at some later stage, you can rip it out, but your YAML files still contain a serialized string, so you don’t lose any information. It’s also usable with ScriptableObjects. I honestly believe it offers a lot of value to any flow.