This is part of an ongoing series where we write a complete 2D game engine in C++ and SFML. A new tutorial is released every Monday. You can find the complete list of tutorials here and download the source code from the projects GitHub page.
In this tutorial we are going to delve into the world of bits and bitwise operations. We will create a bitmask class that will be useful throughout our games engine (including in our input system that we will be starting in the next tutorial).
As we will be jumping straight into discussing operations we can perform on bits, it may be helpful to refresh your memory on what bits actually are. I recommend reading this tutorial.
Bitwise operations are any operations performed on ints and unsigned ints at the binary level, that change the values of individual bits.
It is easiest to explain what bitwise operators are by providing examples of each operation, therefore the table below outlines the bitwise operators we can make use of in C/C++ and provides an example of each one. I’ll provide additional examples when we discuss the implementation of our Bitmask class.
OPERATOR | NAME | WHAT IT DOES | EXAMPLE |
& | AND | The result contains a bit only if it exists in both | 0011 & 1010 = 0010 |
| | OR | The result contains a bit if it exists in either | 0011 | 1010 = 1011 |
^ | XOR (Exclusive OR) | The result contains a bit if it exists in one but not both | 0011 ^ 1010 = 1001 |
<< | LEFT SHIFT | The bits are shifted left by the amount specified. | 0011 << 2 = 1100 |
>> | RIGHT SHIFT | The bits are shifted right by the amount specified. | 1010 >> 2 = 0010 |
~ | ONES COMPLEMENT | Inverts/flips the bits i.e. 0 becomes 1 and 1 becomes 0 | ~1010 = 0101 |
You may notice that some bitwise operators look similar to there logical counterparts (for example & vs &&, and | vs ||), and they do perform a similar function just on a smaller scale.
Start by creating a class called Bitmask. This class will handle all of our bit manipulation.
#ifndef Bitmask_hpp
#define Bitmask_hpp
#include <stdint.h>
class Bitmask
{
public:
Bitmask();
// Overwrites this bitmask.
void SetMask(Bitmask& other);
// Returns binary representation of bitmask.
uint32_t GetMask() const;
// Returns true if bit at pos = 1, else false.
bool GetBit(int pos) const;
// Sets bit at specified pos to 1 or 0 (true or false).
void SetBit(int pos, bool on);
// Sets bit at pos to 1.
void SetBit(int pos);
// Sets bit at pos to 0.
void ClearBit(int pos);
// Sets all bits to 0.
void Clear();
private:
uint32_t bits; // 1.
};
#endif /* Bitmask_hpp */
1. We use uint32_t, a fixed-width 32-bit integer, to store our bitmask. This ensures that no matter what operating system our game is run on, it will always be 32 bits wide. More information on fixed-width integers can be found here.
#include "Bitmask.hpp"
Bitmask::Bitmask() : bits(0) { }
void Bitmask::SetMask(Bitmask& other)
{
bits = other.GetMask();
}
uint32_t Bitmask::GetMask() const
{
return bits;
}
bool Bitmask::GetBit(int pos) const
{
return (bits & (1 << pos)) != 0; // 1
}
// A simple helper method that calls set or clear bit
void Bitmask::SetBit(int pos, bool on)
{
if(on)
{
SetBit(pos);
}
else
{
ClearBit(pos);
}
}
void Bitmask::SetBit(int pos)
{
bits = bits | 1 << pos; // 2
}
void Bitmask::ClearBit(int pos)
{
bits = bits & ~(1 << pos); // 3
}
void Bitmask::Clear()
{
bits = 0;
}
1. Sometimes it is easier to explain how something works with an example.
Let’s say we call GetBit(2), so we want to know the true/false value of the bit at position 2. Let’s also assume the bitmask is 0101. The actual bitmask is 32 bits wide but for the example we’ll keep it simple and use 4 bits.
(bits & (1 << pos)) != 0 can be separated into the following steps:
// We shift 1 by the specified position (2).
// This gives us a 1 bit in the correct position.
1 << 2 = 0100
// We AND the result of the previous step with our bitmask.
// The result will either contain a 1 at the specified position
// (if the bit is turned on in our bitmask), or a
// 0 (if the bit is turned off).
0100 & 0101 = 0100
// Therefore we know we have the bit at position 2 set to 1.
0100 != 0 = true
2. To set a bit to 1 we use the same shift to set a bit at the correct position as we did in the previous example, however this time we OR the result with our bitmask. For example, let’s say we call SetBit(1) and the bitmask is 0101:
// This sets a 1 at the correct position
// (same as the previous example).
1 << 1 = 0010
// Our new bitmask (0111) contains all the 1’s
// it did initially plus we have now set the bit
// at position one to 1.
0010 | 0101 = 0111
We then OR with our bitmask. This will have one of two actions; firstly if our bitmask does not have a bit set in the position 1 then our new bitmask will not have that bit set (as we are using an OR), or if we already had the bit set the OR will do nothing and our bitmask will remain the same.
3. Clearing a bit uses the shift again, but this time we invert its bits, using ones complement, before creating a new bitmask by ANDing the result with our current bitmask. For example, let’s say we call ClearBit(0) and the bitmask is 0101:
1 << 0 = 0001
// This inverts the bits, so all bits but the
// position we’ve requested to clear are set to 1.
~0001 = 1110
1110 & 0101 = 0100
Our new bitmask has the bit at position 0 converted from 1 to 0.
These are the only methods we’ll need for the foreseeable future, although as with everything we write at this stage, we will probably be extending it in future.
It may not be too obvious how we are going to make use of this class at the moment, but we will shortly find out as we look at creating an input system for our game engine.
As always, if you have any suggestions for what you would like covered or are having any trouble with the code then let me know in the comments.
Thank you for reading 🙂