Bit Mask Concept

Unity - Deep Cover of Bitwise and Binary Numbers Concept and Implementation

Irfan Yigit Baysal
10 min readDec 15, 2023

Introduction

Hello everyone, in this article, I will explain bit mask concept with some examples. The contents are respectively,

  • Bit Explanation
  • Binary Number Explanation And How They Are Calculated
  • Bitwise Operations Explanation
  • Bitwise Shifting Operations Explanation
  • Bitwise Operations Implementation Examples
  • Bitwise Shifting Operations Implementation Examples
  • Advantages
  • Summary
  • Links That You Want To Check

If you’re ready, let’s start!

Explanation

Bit

First, let’s define a bit. A binary digit is called a bit. It is a computer’s smallest bit of data, and its values can only be 0 or 1.

Binary Number

Let’s remind the binary numbers and its calculations before understanding bitmask concept with an example of converting binary number to decimal numbers.

Converting binary number to decimal numbers

You can also test this converting calculations by adding this code below in Unity.

 int number = 21;
string binaryString = Convert.ToString(number,2);
Debug.Log($"Number: {number}, Binary: {binaryString}");

The leading 0s are omitted and that’s why we have 10101 instead of 010101.

Bitmask

Bit masks allow many values to be stored and retrieved simultaneously from a single variable. Basically, Bit masks are an efficient way to store an array of booleans. It is a technique in computer programming to perform bitwise operations on binary numbers.

Bitwise

Bitwise operations are contrasted by byte-level operations which characterize the bitwise operators’ logical counterparts, the AND, OR, NOT operators.

  • AND (&) Takes two numbers as operands and performs a bitwise AND operation. Each bit of the result is 1 only if both corresponding bits of the operands are 1.
Bitwise AND (&) Operation Result Table
  • OR (|) Performs a bitwise OR operation. Each bit of the result is 1 if at least one corresponding bit of the operands is 1.
Bitwise OR (|) Operation Result Table
  • XOR (^) Performs a bitwise XOR (exclusive OR) operation. Each bit of the result is 1 if the corresponding bits of the operands are different.
Bitwise XOR (^) Operation Result Table
  • NOT (~) Flips the bits of its operand, changing 1s to 0s and vice versa.
Bitwise NOT (~) Operation Result Table

Shifting

Right Shift (>>) All bits on that block move to the right position. The rightmost bit is lost and a 0 is added from the leftmost bit.

Our pattern is for right shift is

  • Remove rightmost bit (1)
  • Move all bits to right position (2)
  • Add 0 to leftmost bit (3)

Let’s also exemplify this shifting operations with the our binary number example that we used above which is number 21 also.

So, if we shift a byte to the right, we divide its integer value by 2.

Left Shift (<<) All bits on that block move to a left position. The leftmost bit is lost, a 0 is added from the rightmost bit.

Our pattern is for left shift is

  • Remove leftmost bit (1)
  • Move all bits to left position (2)
  • Add 0 to rightmost bit (3)

When we shift a byte to the left, we multiply its integer value by 2.

Summary of shifting operations

Implementation

In order to exemplify bitwise operations, let’s create an enum named SoldierType using [Flags].

[Flags]
public enum SoldierType
{
None = 0,
Private = 1,
Corporal = 2,
Sergeant = 4,
Lieutenant = 8,
Major = 16,
General = 32,
All = Private | Corporal | Sergeant | Lieutenant | Major | General
}

Let’s also write their binary numbers too. Always remember that The leading 0s can be omitted.

As a reminder, Flags allow an enum value to contain many values. An enum type with the [Flags] attribute can have multiple constant values assigned to it.

SoldierType enum Flag lookout in inspector

So lets start implementing bitwise operations as we explained above and start with the OR operator.

  • OR ( | )
public void PerformOrOperation()
{
soldierType = SoldierType.Major | SoldierType.General;
}
PerformOrOperator method

As you can see in the gift, PerformOrOperation() method declares soldier type as Major and General.

We can also set soldierType enum by using their integer numbers. In order to do that we can use our simple converter method as we also used in previous examples above.

 public void GetDataFromRequestedSoldierType(SoldierType requestedSoldierType)
{
var binaryString = Convert.ToString((int)requestedSoldierType, 2);
Debug.Log($" Name : {requestedSoldierType}, Number: {(int)requestedSoldierType}, Binary: {binaryString}");
}
Simple Editor Button for Getting Data From Requested Soldier Type

So, according to log, our soldier type number is 48 and binary is 110000. So let’s also implement to set soldier type with this number.

public void SetTypeFromNumber(int number,ref SoldierType type)
{
type = (SoldierType)number;
}

Let’s also check the result by checking the binary numbers of Major and General with the OR operand. So check OR operands table out above and calculate its binary number.

  • Green one is the General’s binary number.
  • Orange one is the Major’s binary number.

So red one is the output and it matches with the binary that is in the debug log screenshot.

OR operations. You can check it on OR operand table above
  • AND ( & )

Let’s create a condition and what we are going to do is, set our soldier type and control soldier type by using AND operand whether has a General or not.

public void PerformAndOperation()
{
soldierType = SoldierType.Major | SoldierType.General;
bool hasAtLeastAGeneral = (soldierType & SoldierType.General) != 0;
Debug.Log(hasAtLeastAGeneral);
}

Lets analyze this code and try to find the answer by focusing on that operation (soldierType & SoldierType.General).

We already calculated that our soldier type binary number is 110000 in OR operand’s example right? So let’s place it the first place.

  • Green one is the Soldier type’s binary number
  • Orange one is the General’s binary number
AND operations. You can check it on AND operand table above

So red one is the output according to AND Operand. This binary number is the same number with General’s binary number which is orange one in this example. We can say that, (soldierType & SoldierType.General) = General.

bool hasAtLeastAGeneral = (soldierType & SoldierType.General) != 0;

As a result, we can say that ‘hasAtLeastAGeneral’ flag is true. PerformAndOperation() method returns true as a result.

  • XOR (^)

In this example soldier type is Lieutenant, Major and General. With XOR we want to exclude the Major in PerformXOROperation() as below.

public void PerformXOROperation()
{
soldierType ^= SoldierType.Major;
}

Let’s also calculate with binary numbers to make sure.

  • Green one is our soldier type binary numbers for this example.
  • Orange one is our Major’ s binary number
Bitwise AND (&) Operation Result Table

The output is (red one) matches with the binary number in log.

  • NOT (~)

For this example, we set our soldier type as ‘None’ and create a method named PerformNotOperation(). This method operates the NOT operand.


public void PerformNotOperation()
{
soldierType = ~ SoldierType.Major;
}

As a result, soldier type because almost all but not major. Because in this code we use say that soldier type become not major above.

Let’s also calculate with binary numbers to make sure.

  • Green one is our None’s binary number which is 000000
  • Orange one is our Major’ s binary number which is 010000

The output is (red one) matches with the binary number in log.

  • RIGHT SHIFT (>>)

We have already explained right shift in Shifting part and we summarized the right shift as dividing its integer value by 2. Now let’s also see this explanation as implementation.

We declared SoldierType enum above before and let’s also copy it here for quick checking.

[Flags]
public enum SoldierType
{
None = 0,
Private = 1,
Corporal = 2,
Sergeant = 4,
Lieutenant = 8,
Major = 16,
General = 32,
All = Private | Corporal | Sergeant | Lieutenant | Major | General
}

For instance, let’s take Sergeant type as SoldierType and perform our implementation over it. As you can see Sergeant = 4 in this flag.

public void PerformRightShiftOperation()
{
soldierType = (4 >> 1);
}

Let’s start coding as you see above. I get 4 and I want to shift it 1 as a right shift. ( 4>>1). In order to get it work we also should cast this operation to SoldierType.

public void PerformRightShiftOperation()
{
soldierType = (SoldierType)(4 >> 1);
}

Right shift means actually dividing its integer by 2, therefore with a quick check to SoldierType you can see that Sergeant = 4, and when we divide 4by 2 it becomes 2. 4 is Corporal in our SoldierType enum. You can quickly answer that 4 >> 1 is actually Corporal in our example. Let’s also prove within Unity.

Do not forget that corporal’s binary numbers are actually for this example is 000010 but unity omits the leading 0s. Therefore it is written as 10 here.

You may also check this result by calculating their binary numbers as we explained above examples. It would be a good practice :).

  • LEFT SHIFT (<<)

We already explained left shift in Shifting part and we summarized the right shift as multiplying its integer value by 2. Let’s use the same example we have done in right shift part.

public void PerformLeftShiftOperation()
{
soldierType = (SoldierType)(4 << 1);
}

Sergeant = 4, and for this time we multiply it and ,when we multiply 4 by 2 it becomes 8. 8 is Lieutenant in our SoldierType enum. You can quickly answer that 4 << 1 is actually Lieutenant in our example. Let’s also prove within Unity.

Advantages

  1. Memory Efficiency: Bitmasks enable efficient storage of multiple boolean flags within a single variable, reducing memory overhead compared to using separate variables for each flag. This is particularly beneficial when dealing with a large number of binary options.
  2. Performance Optimization: Bitwise operations (AND, OR, XOR) are fast and efficient, making bitmask manipulation a performant way to check, set, or unset multiple flags simultaneously. This is crucial for real-time applications like games where performance is a priority.
  3. Compact Representation: Bitmasks allow the representation of a complex set of states in a compact and readable form, making code more concise and easier to manage. This is especially valuable when dealing with a large number of possible states or configurations.
  4. Flag-based Logic: Bitmasks facilitate the use of flag-based logic, allowing developers to work with combinations of states in a straightforward manner. This leads to cleaner and more maintainable code compared to using traditional boolean variables or enums.
  5. Conditional Testing: Bitmasks enable efficient testing of individual flags using bitwise AND or OR operations. This simplifies conditional testing for specific states and allows for more expressive and flexible logic.
  6. Ease of Serialization: When saving or transmitting state information, bitmasks can be easily serialized into a single numerical value. This streamlined representation simplifies the process of saving and loading game states or sending information over a network.
  7. Enumeration of Options: By assigning each bit a specific meaning, developers can create enumerations that define the various options or states in a clear and organized manner. This improves code readability and maintenance.

Summary

In summary, the bitmask concept in Unity provides an efficient and powerful tool for managing multiple boolean flags, contributing to improved memory usage, performance, code readability, and maintainability in game development. It would be used in input handling, collision layers, state machines, permission systems especially in AI based implementations, inventory systems, quest tracking, AI Behavior flags and many more examples.

--

--

Irfan Yigit Baysal
Irfan Yigit Baysal

Written by Irfan Yigit Baysal

Unity Game Developer at Matchingham Games

No responses yet