If you’re a game developer chances are you’re familiar with the need to describe different variations ofan attribute. Whether it’s the type of an attack (melee, ice, fire, poison, …) or the state of an enemy AI (idle, alerted, chasing, attacking, resting, …) you can’t escape this. The most naive way of implementing this is simply by using constants:
public static int NONE = 0;public static int MELEE = 1;public static int FIRE = 2;public static int ICE = 3;public static int POISON = 4;public int attackType = NONE;
The downside is that you have no actual control over the values you can assign to attackType
: it can be any integer, and you can alsodo dangerous things such as attackType++
.
The enum construct
Luckily, C# has aconstruct called enum(for enumeration) whichhas been specifically designed for these situations:
// Outside your classpublic enum AttackType { None, Melee, Fire, Ice, Poison}// Inside your classpublic AttackType attackType = AttackType.None;
The definition of an enum creates a type which cansupport only a limited range or values. These values are given symbolic labelsfor clarity andare alsoreturned as string when needed:
attackType = AttackType.Poison;Debug.Log("Attack: " + attackType); # Prints "Attack: Poison"
Internally, every labelhas an integer value. Enums starts from zero and every new label isassigned the next integer number. None
is zero, Melee
is one, Fire
is two and so on.You can change that by explicitly changing the value of a label:
public enum AttackType { None, // 0 Melee, // 1 Fire, // 2 Ice = 4, // 4 Poison // 5}
Casting an enum to int will return its integer value. To be fair, enumsare actual integers.
What makes enums even so interesting is the fact that they are automatically integrated in the Unityinspector. If a public field is an enum, it will conveniently appear like a dropdown menu:
Enums and Flags
Thevastmajority of developers use enums just as we’ve seen before.There’s much more we can do with them though. The first limitation is that standard enums can only hold a value at a time. What if we are using a melee weapon with a fire attack (a fire sword?!)? To solve this problem,enumscan be decorated with [Flags]
. This allows them to be treated as bit masks, storing multiple values between them:
[Flags] public enum AttackType { None = 0, Melee = 1, Fire = 2, Ice = 4, Poison = 8}// ...public AttackType attackType = AttackType.Melee | AttackType.Fire;
In theexample above, attackType
both holds Melee
and Fire
values. We’ll see later how it is possible to retrieve these values. But first we need to understand how this is actually working.Enums are store as integers; when you have consecutive numbers, their bit representations look like this:
// Consecutivepublic enum AttackType { // Decimal // Binary None = 0, // 000000 Melee = 1, // 000001 Fire = 2, // 000010 Ice = 3, // 000011 Poison = 4 // 000100}
If we want to use [Flags]
at its best, we should useonly powers of two for the values of our labels. As you can see below, this means that every non-zero label has exactly one 1
in its binary representation, and that they are all in different positions:
// Powers of two[Flags] public enum AttackType { // Decimal // Binary None = 0, // 000000 Melee = 1, // 000001 Fire = 2, // 000010 Ice = 4, // 000100 Poison = 8 // 001000}
At this stage,the variable attackType
can be seen as a series of bits, each one indicating if it has or not a certain property. If the first bit is one, it is a melee attack; if the second bit is one, it is a fire attack, if the third bit is one it is an ice attack, and so on. It is important to noticein order for this to work,labels have to be manually initialised as powers of two. We will see later in this post how to do it more elegantly. Lastly, since enums are normally stored into Int32
, it’s unwise to have an enum with more then 32 different labels.
To be fair, you canuse enums even without [Flags]
. The only thing it does is allowing a nicer output ofenums when they are printed.
Bitwise operators
A bit mask is, essentially, an integer value in which several binary property (yes/no) are independently stored in its bit. In order to pack and unpackthem we need some special operators. C#calls them bitwise operator, because they work on a bit to bit basis, ignoring carries unlikelyaddition and subtraction operators.
Bitwise OR
Setting a property is possible using the bitwise OR:
attackType = AttackType.Melee | AttackType.Fire;// ORattackType = AttackType.Melee;attackType |= AttackType.Fire;
What the bitwise ORdoes is settingthe bit in the i-th position to 1
if either one of itsoperands has the i-th bit to1
.
// Label Binary Decimal// Melee: 000001 = 1// Fire: 000010 = 2// Melee | Fire: 000011 = 3
If you print attackType
you’ll get a user-friendly result: Melee | Fire
. This unfortunately doesn’t happen with the inspector; Unity doesn’t recognise the new type and simply leaves thefield empty:
If you want themixed type to appear, you’ll have todefine it manually in the enum:
[Flags] public enum AttackType { // Decimal // Binary None = 0, // 000000 Melee = 1, // 000001 Fire = 2, // 000010 Ice = 4, // 000100 Poison = 8, // 001000 MeleeAndFire = Melee | Fire // 000011}
Bitwise AND
Thecomplementary operator to the bitwise OR is the bitwise AND. It works in the exact same way, with the exception that when appliedwith twointegers it keeps only the bits which are set in both of them. While bitwise OR is used to set bits, bitwise AND is typicallyused tounpack property previously stores in an integer.
attackType = AttackType.Melee | AttackType.Fire;bool isIce = (attackType & AttackType.Ice) != 0;
Applying the bitwise AND between attackType
and AttackType.Ice
sets all the bits to zero, excepts the one relative to AttackType.Ice
itself; its final value is determined by attackValue
. If the attack vas indeed an icy one, the result will be exactly AttackType.Ice
; otherwise zero:
// Label Binary Decimal// Ice: 000100 = 4// MeleeAndFire: 000011 = 3// MeleeAndFire & Ice: 000000 = 0// Fire: 000010 = 2// MeleeAndFire: 000011 = 3// MeleeAndFire & Fire: 000010 = 2
If bitwise operators are making your head spinning, .NET 4.0 has introduced the method HasFlag
which can be conveniently used as follow:
attackType.HasFlag(AttackType.Ice);
Now,you need to be careful about the fact that None
has been represented with the value zero. As a result, our original approach will fail to check for None
:(attackType & AttackType.None) != 0
always return false, since attackType & 0
is always zero. A possible way to avoid this is to check against the original value:
bool isNone = (attackType & AttackType.None) == AttackType.None;
When it comes toNone
, this behaviour might or might not be what you want. Just be aware that the standard .NET implementation of HasFlag
usesour latest example. If you don’t want to go crazy, you can also defineNone
as 1 instead. Just remember thatis preferableto always have a zero value in an enum.
Bitwise NOT
There isanother useful bitwise operator, which is the bitwise NOT. What it does is simply inverting all the bits of an integer. This can be useful, for instance, to unset a bit. Let’s say we want our attack to stop being firey and become icy instead:
attackType = AttackType.Melee | AttackType.FireattackType &= ~ AttackType.Fire;attackType |= AttackType.Ice;
By negating the property AttackType.Fire
we are left with a bitmask which has all 1
s, except for a zero in the position relative to the fire property. When AND-ed with attackType
, it will leave all the other bits unchanged and unset the fire property.
Bitwise XOR
After OR, AND and NOT, we cannot not mention the bitwise XOR. As the name suggest, itis used to xor bits in the same position of an integer variable. The xor (or exclusive or) of two binary values is true only if one or the other is true, but not both. This has a very important meaning for bit masks, since it allows to toggle a value.
attackType = AttackType.Melee | AttackType.Fire;attackType ^= AttackType.Fire; // Toggle fireattackType ^= AttackType.Ice; // Toggle ice
Bitwise shifts
The last two operatorsto work with bit masks are the bitwise shifts. Taken a number, they literally shift its bits right (>>) or left (<<).If you have a decimal number, let’s say “1” and you shift it of one position to theleft, you’ll have “10”. Another shift and you’ll get “100”.If shifting one position in base ten is equivalent to multiply (or divide) by ten, shifting one position in base two is equivalent to multiply (or divide) by two. This is why bitwise shifts can be used to easily create powers of two:
[Flags] public enum AttackType { // // Binary // Dec None = 0, // 000000 0 Melee = 1 << 0, // 000001 1 Fire = 1 << 1, // 000010 2 Ice = 1 << 2, // 000100 4 Poison = 1 << 3, // 001000 8}
Conclusion
This postsintroduces enums, flags and bitwise operators.You can use the following functions to better work with them:
public static AttackType SetFlag (AttackType a, AttackType b){ return a | b;}public static AttackType UnsetFlag (AttackType a, AttackType b){ return a & (~b);}// Works with "None" as wellpublic static bool HasFlag (AttackType a, AttackType b){ return (a & b) == b;}public static AttackType ToogleFlag (AttackType a, AttackType b){ return a ^ b;}
The technique described in this postis flexible enoughto be adapted not just to attack types but also to inventory systems, itemproperties and finite state machines.
You should also check this other tutorial on how toextend the inspector for a more user friendlyenum interface:
If instead you prefer a dropdown menu, you should check Unity’s EnumMaskField.
💖 Support this blog
This websites exists thanks to the contribution of patrons on Patreon. If you think these posts have either helped or inspired you, please consider supporting this blog.
📧 Stay updated
You will be notified when a new tutorial is relesed!
📝 Licensing
You are free to use, adapt and build upon this tutorial for your own projects (even commercially) as long as you credit me.
You are not allowed to redistribute the content of this tutorial on other platforms. Especially the parts that are only available on Patreon.
If the knowledge you have gained had a significant impact on your project, a mention in the credit would be very appreciated. ❤️🧔🏻