Flags Enum "Everything" value returns -1

Hi all!

I have a question related to a enum I need to use as a bitmask.
Basically, my enum looks like this:

[Flags]
public enum DebuffStatus
{
// Powers of 2
NoDebuffs = 0b0000,
Blind = 0b0001,
Mute = 0b0010,
Poison = 0b0100,
Petrified = 0b1000,
}

The idea is that each value represents a debuff status. All the combinations are possible (except adding NoDebuffs to something else).

Due to some idea I have, I need to preset the values in a scriptable object and then build a dictionary to show the related sprite, i.e:

If I set the serialize value in Unity Editor to Blind | Poison, the character will show one specific sprite related to these two statuses.

The problem comes when I set all the statuses. Automatically Unity Editor sets the value to Everything as you can see here:
9629969--1368059--upload_2024-2-7_18-37-26.png

And when adding the key to the dictionary, it sets a -1, when I was expecting a 15 (all statuses to 1), so I am not able to retrieve the related sprite afterward:
9629969--1368062--upload_2024-2-7_18-41-21.png

Is there any solution to avoid this problem? I am completely stuck on this after a few hours searching for solutions, and I am not able to get any that work properly :(

Thank you very much in advance!

Kind regards

If you're getting -1, that's indeed unexpected behavior because you should be getting a valid result. Unfortunately, you'll need to set up Debug.Log statements in places where you're processing something with this enumeration and check at which point you're getting something unexpected. You're probably performing some strange conversion.


Use your own entry.

Everything = Blind | Mute | Poison | Petrified
1 Like

Unity doesn't do anything fancy when it detects "Everything." It just sets all the bits to 1, which for an int, the default backing type for an enum, is the binary representation of -1.

You can add combined values to the enum, or declare them externally as constant values. Which one you choose affects whether the value is selectable in the inspector dropdown field or not. If you want to limit which bits might be set to a specific range of bits, you'll need your own bit mask for this.

[Flags]
public enum DebuffStatus
{
    Blind = 0x1,
    Mute = 0x2,
    Poison = 0x4,
    Petrified = 0x8,
    All = Blind | Mute | Poison | Petrified,
}

public const DebuffStatus AllDebuffs = DebuffStatus.Blind | DebuffStatus.Mute | DebuffStatus.Poison | DebuffStatus.Petrified;

But, be warned that even if you define your own combined value for all values, if you select it in the editor, or if you manually select all values, Unity will just set all the bits to 1 again, giving you -1.

So, you will need to apply the bitmask that represents what you consider to be all valid flags to the serialized value before using it. Here are some examples of masking the serialized value to the expected range.

Status &= DebuffStatus.All;
Status &= Foo.AllDebuffs;
Status &= (DebuffStatus)0xF;
int key = (int)Status & 15;

if((Status & DebuffStatus.All) == DebuffStatus.All)
    Debug.Log("All set!");
5 Likes

Thanks guys for the answers!

[quote]
If you're getting -1, that's indeed unexpected behavior because you should be getting a valid result. Unfortunately, you'll need to set up Debug.Log statements in places where you're processing something with this enumeration and check at which point you're getting something unexpected. You're probably performing some strange conversion.
[/quote]

About this, I am not able to find where... the only thing I am doing is setting the value in the Inspector and reading it. All the first 14 values are working fine, but last value is generting a -1.

[quote]
- Everything = Blind | Mute | Poison | Petrified
[/quote]

I already tired this and same behaviour...

I will try to debug further just in case I am missing something... Thanks once again for your help!


I just tested and unfortunately even if you specify your own Everything or All, then select that value in the editor, or manually select each flag, Unity will just automatically serialize it to -1.

1 Like

I see... so -1 is expected then. OK! I will give a try to your suggestions! Thank you very much :)

I wouldn't say it is expected, per se, but it is what it is.

Just some extra bits of trivia: If you were to cast -1 to (uint) it would just become uint.MaxValue. If you want an int with only the sign bit, that would be int.MinValue. So if you want to mask an int's bits to keep everything except the sign bit that would be someInt &= ~int.MinValue; Random information, but it's rattling around in my head, so...

2 Likes

Status &= DebuffStatus.All;

This solution worked perfectly in my case! Thanks a million CodeRonnie, you are awesome :)

1 Like


Strange I used same example and I get 15 each time.

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

public class Test : MonoBehaviour
{
    [System.Flags]
    public enum DebuffStatus
    {
        NoDebuffs = 0b0000,
        Blind = 0b0001,
        Mute = 0b0010,
        Poison = 0b0100,
        Petrified = 0b1000,
    }

    public DebuffStatus debuffStatus;

    void Start()
    {
        debuffStatus = (DebuffStatus.Blind | DebuffStatus.Mute | DebuffStatus.Poison | DebuffStatus.Petrified);
        Debug.Log(debuffStatus); // result 15
    }
}

Edit: Nevermind, now I get it. If you check Everything in editor you get -1.

3 Likes

Yes of course, because you are manually setting it to that value in script right before you log it. Try removing that line, setting the values in the editor, and then pressing play.

2 Likes

With the above in mind, and some assistance from ChatGPT, here's a custom property drawer that will force the correct value.

[CustomPropertyDrawer(typeof(DebuffStatus))]
public class MyEnumPropertyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        DebuffStatus currentFlags = (DebuffStatus)property.intValue;
        DebuffStatus newFlags = (DebuffStatus)EditorGUI.EnumFlagsField(position, label, currentFlags);

        if (Convert.ToInt32(newFlags) == -1)
        {
            property.intValue = (int)DebuffStatus.Everything;
        }
        else
        {
            property.intValue = (int)newFlags;
        }
    }
}
2 Likes

Just because I'm bored I generalized this... in this case I made it an attribute based thing, but you could easily just attach it as a custompropertydrawer for any type adhoc:

    public class EnumFlagsConstrainedAttribute : PropertyAttribute { }

    [CustomPropertyDrawer(typeof(EnumFlagsConstrainedAttribute))]
    public class EnumFlagsConstrainedPropertyDrawer : PropertyDrawer
    {

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            if (this.fieldInfo == null || !this.fieldInfo.FieldType.IsEnum)
            {
                EditorGUI.LabelField(position, label, new GUIContent("Unsupported data type: " + property.type));
                return;
            }

            System.Enum evalue = (System.Enum)System.Enum.ToObject(this.fieldInfo.FieldType, property.intValue);
            EditorGUI.BeginChangeCheck();
            evalue = EditorGUI.EnumFlagsField(position, label, evalue);
            if (EditorGUI.EndChangeCheck())
            {
                int mask = 0;
                foreach (var i in System.Enum.GetValues(this.fieldInfo.FieldType)) mask |= System.Convert.ToInt32(i); //use Convert incase the enum is not an int
                property.intValue = System.Convert.ToInt32(evalue) & mask;
            }
        }

    }

//EXAMPLE USAGE:
    [System.Flags]
    public enum DebuffStatus
    {
        // Powers of 2
        NoDebuffs = 0b0000,
        Blind = 0b0001,
        Mute = 0b0010,
        Poison = 0b0100,
        Petrified = 0b1000,
    }

    public class zTest01 : MonoBehaviour
    {

        [EnumFlagsConstrained]
        public DebuffStatus status;

    }

No need for the 'Everything' in the enum itself.

With that said... there is nothing in C# stopping you from casting any arbitrary number to an enum type. Values that aren't reprsented in the enum are still valid values (case in point every combination of your masks aren't actually named values in your enum... 3 or 0b0011 isn't defined). So you can't guarantee that someone doesn't set this to some arbitrary value.

Generally your 'mask' values are going to be operated on with something like the & operator so unrelated bits don't really matter. They just fall out of the operation. This is why unity just shoves a -1 in there. The auxiliary bits don't matter in 99% of scenarios.

But if you're going to use this dictionary... you should probably mask them off when sticking them in the dictionary.

const DebuffStatus MASK_ALL_DEBUFFSTATUS = (DebuffStatus)15;
//...
mydict[status & MASK_ALL_DEBUFFSTATUS] = somevalue;
3 Likes