Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question Multiple Enum Inspector

Discussion in 'Scripting' started by unity_4i4iZ2tDGdy1AA, May 6, 2023.

  1. unity_4i4iZ2tDGdy1AA

    unity_4i4iZ2tDGdy1AA

    Joined:
    May 2, 2023
    Posts:
    6
    Hello, I needed a way to select and store multiple Enums. I found this on the forum.

    Attribute
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System;
    5. using System.Linq;
    6.  
    7. public class EnumFlagsAttribute : PropertyAttribute
    8. {
    9.     public EnumFlagsAttribute() { }
    10.  
    11.     public static List<int> GetSelectedIndexes<T>(T val) where T : IConvertible
    12.     {
    13.         List<int> selectedIndexes = new List<int>();
    14.         for (int i = 0; i < System.Enum.GetValues(typeof(T)).Length; i++)
    15.         {
    16.             int layer = 1 << i;
    17.             if ((Convert.ToInt32(val) & layer) != 0)
    18.             {
    19.                 selectedIndexes.Add(i);
    20.             }
    21.         }
    22.         return selectedIndexes;
    23.     }
    24.     public static List<string> GetSelectedStrings<T>(T val) where T : IConvertible
    25.     {
    26.         List<string> selectedStrings = new List<string>();
    27.         for (int i = 0; i < Enum.GetValues(typeof(T)).Length; i++)
    28.         {
    29.             int layer = 1 << i;
    30.             if ((Convert.ToInt32(val) & layer) != 0)
    31.             {
    32.                 selectedStrings.Add(Enum.GetValues(typeof(T)).GetValue(i).ToString());
    33.             }
    34.         }
    35.         return selectedStrings;
    36.     }
    37.  
    38.     public static bool IsSelected<T>(T val, int index) where T : IConvertible
    39.     {
    40.         int layer = 1 << index;
    41.         return ((Convert.ToInt32(val) & layer) != 0);
    42.     }
    43.  
    44.     public static bool IsSelected<T>(T val, string name) where T : IConvertible
    45.     {
    46.         int index = Enum.GetNames(typeof(T)).ToList().IndexOf(name);
    47.         int layer = 1 << index;
    48.         return ((Convert.ToInt32(val) & layer) != 0);
    49.     }
    50. }
    Drawer
    Code (CSharp):
    1. #if UNITY_EDITOR
    2. using UnityEngine;
    3. using System.Collections;
    4. using System;
    5. using UnityEditor;
    6.  
    7. [CustomPropertyDrawer(typeof(EnumFlagsAttribute))]
    8. public class EnumFlagsAttributeDrawer : PropertyDrawer
    9. {
    10.     public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _label)
    11.     {
    12.         _property.intValue = EditorGUI.MaskField(_position, _label, _property.intValue, _property.enumNames);
    13.     }
    14. }
    15. #endif
    16.  
    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
     
  2. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    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.
     
    unity_4i4iZ2tDGdy1AA likes this.
  3. unity_4i4iZ2tDGdy1AA

    unity_4i4iZ2tDGdy1AA

    Joined:
    May 2, 2023
    Posts:
    6
    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.
     
  4. unity_4i4iZ2tDGdy1AA

    unity_4i4iZ2tDGdy1AA

    Joined:
    May 2, 2023
    Posts:
    6
    Here is a Video of the problem:

     
  5. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    You definitely want to make a provision for the mixed values in your drawer.
    Let me check the docs, there is an attribute.
     
    unity_4i4iZ2tDGdy1AA likes this.
  6. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    6,015
    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?
     
  7. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
  8. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    This is how I handled this for my case (there's more in this example than you need)
    Code (csharp):
    1. mixedGUI(_target.hasMultipleDifferentValues, () => {
    2.   var newVal = drawDropDownSelector(rect, clamp(val, _records.Length), usesTooltips);
    3.   if(val != newVal) _target.stringValue = _records[newVal].name;
    4. })
    5.  
    6. void mixedGUI(bool condition, Action action) {
    7.   var saved = EditorGUI.showMixedValue;
    8.   EditorGUI.showMixedValue = condition;
    9.   action.Invoke();
    10.   EditorGUI.showMixedValue = saved;
    11. }
    12.  
    13. int drawDropDownSelector(Rect rect, int selected, bool usesTooltips)
    14.   => EditorGUI.Popup(rect, GUIContent.none, selected, _cached??= extractGUIContentColumn(usesTooltips));
    15.  
    You can exactly how this works if you grab the code from here. It's made for enums specifically.
     
    unity_4i4iZ2tDGdy1AA likes this.
  9. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Is this for the flags? Yes Unity already does flag enums, hm.
     
    unity_4i4iZ2tDGdy1AA likes this.
  10. unity_4i4iZ2tDGdy1AA

    unity_4i4iZ2tDGdy1AA

    Joined:
    May 2, 2023
    Posts:
    6
    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...
     
  11. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    6,015
    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.
     
  12. unity_4i4iZ2tDGdy1AA

    unity_4i4iZ2tDGdy1AA

    Joined:
    May 2, 2023
    Posts:
    6
    Thanks for the fast help and nice responses! :)
     
  13. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    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.
     
    unity_4i4iZ2tDGdy1AA likes this.
  14. unity_4i4iZ2tDGdy1AA

    unity_4i4iZ2tDGdy1AA

    Joined:
    May 2, 2023
    Posts:
    6
    Thank you! :)
     
    orionsyndrome likes this.
  15. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    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.