Quick Tip: Enum Flags

Pack your bits up

;

Posted 11/30/2015 17:49:18 in Homestead

Updated 07/04/2017 14:00:54

Whoops, should've checked my work! It turns out that Unity uses an older version of C# that doesn't have the System.Flags features, but the concepts are still valid! I've included a little utility class that I use myself to gain the same functionality as enum.HasFlags. Sorry for any confusion!

This week I learned of a neat feature in C# that can be very handy when you're working with bitflags and bitfields. If you're unfamiliar with the concept, a "bitfield" is the idea of using the bits in a byte as a set of on-off switches instead of as a representation of a number, kind of like an array of boolean values. In Unity, this idea is most often used in Layer Masking, but it has lots of uses. In my case, I'm using bitflags to add attributes to my inventory system's items.

To understand what a bitflag does, you need to think of the binary representation of a byte. It's comprised of 8 bits, which can be either 1 or 0. Here are a few examples of decimal representations:

cs


Decimal   Binary
-------   ------
0:        00000000
1:        00000001
2:        00000010
193:      11000001
255:      11111111
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

When we get into the idea of bitflags, we're more interested in the bits than the actual decimal value. In a byte, we get 8 bits, which we can arbitrarily use as 8 independent true/false values. We can combine them in any way, and there will always be a unique decimal equivalent. Also, you may notice that each bit "place" is a power of two.

cs


Bitflag   Binary    Decimal
-------   ------    ------- 
1:        00000001  1
2:        00000010  2
4:        00001000  8
8:        10000000  128
2&3:      00000110  6
4&7&1:    01001001  73
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

You use the data by employing bitwise (a.k.a. logical) operators. Here's a simple example to check whether flag 4 is present in a particular bitfield:

cs


// Set bits 1, 2, 4, and 8
// In binary, 10001011
byte bitField = (byte)1 | (byte)2 | (byte)8 | (byte)128;
// The bit we're checking for
// In binary, 00001000
byte droppableFlag = (byte)8;
// Perform the operation
// In binary:
// 10001011
// 00001000
// -------- (AND)
// 00001000 (Equivalent to droppableFlag)
bool isDroppable = bitField & droppableFlag == droppableFlag;
// result is true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

As you can see, this is a really wonky and hard-to-read way of going about it. Fortunately, C# has a nice attribute that makes this much more usable! You can pack your flags into an enum, and then you can use that enum instead of doing the calculations in your code. Here's that same example, but with an enum and some arbitrary flag names:

cs


enum ItemFlags : byte {
    None        = 0,       // Decimal: 0
    Edible      = 1 << 0,  // Decimal: 1
    Storable    = 1 << 1,  // Decimal: 2
    Tool        = 1 << 2,  // Decimal: 4
    Droppable   = 1 << 3,  // Decimal: 8
    Giftable    = 1 << 4,  // Decimal: 16
    Sellable    = 1 << 5,  // Decimal: 32
    Perishable  = 1 << 6,  // Decimal: 64
    Special     = 1 << 7   // Decimal: 128
}

// ...

ItemFlags item = ItemFlags.Edible |
                 ItemFlags.Storable |
                 ItemFlags.Droppable |
                 ItemFlags.Special;

bool isDroppable = Util.HasFlag(item, ItemFlags.Droppable);
// result is true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

Now that's much clearer to both other readers of your code and yourself! You can use bitwise operators with the enum, and I've created a little utility class called "Util" that has a function to check the flags (you can put this in a separate file anywhere in your project):

cs


public static class Util {
    public static bool HasFlags(ItemFlags target, ItemFlags query) {
        return (target & query) == query;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

That's all I'm going to write about this now, but if you're either lost or interested in reading more, I'd recommend either the MSDN page on the System.Flags attribute (which has some info, but unfortunately won't be available in Unity until they update the version of Mono that they're using) or this tutorial, which goes into more detail than I have here. I'm using this technique in Homestead's inventory system, and it's already saved me several headaches! Happy coding, and see you next week!

Comments

Today