BitMasks are flags used to describe an item in a binary format
so imagine you have 8 ways to describe something. (In Spritekit you have 32)
We can fit these 8 things into a single byte, since 8 bits are in a byte, allowing us to save space and perform operations faster.
Here is an example of 8 descriptions
Attackable 1 << 0
Ranged 1 << 1
Undead 1 << 2
Magic 1 << 3
Regenerate 1 << 4
Burning 1 << 5
Frozen 1 << 6
Poison 1 << 7
Now I have an archer and want to classify him. I want to say he is a living friendly unit that is ranged
I would use the categoryBitmask
to classify him:
archer.categoryBitmask = Ranged
This would be represented in 1 byte as
00000010
||||||||_ Attackable
|||||||_ Ranged
||||||_ Undead
|||||_ Magic
||||_ Regenerate
|||_ Burning
||_ Frozen
|_ Poison
Now let’s say his arrows are fire arrows, I would classify this like:
arrow.categoryBitmask = Burning
00100000
||||||||_ Attackable
|||||||_ Ranged
||||||_ Undead
|||||_ Magic
||||_ Regenerate
|||_ Burning
||_ Frozen
|_ Poison
and finally, we have a zombie that can be hit and regenerates over time
zombie.categoryBitmask = Attackable + Undead + Regenerate
00010101
||||||||_ Attackable
|||||||_ Ranged
||||||_ Undead
|||||_ Magic
||||_ Regenerate
|||_ Burning
||_ Frozen
|_ Poison
Now I want my arrow to only hit Attackable
sprites (zombie in this case)
I would set the contactTestBitmask
to tell the arrow what he can hit
arrow.contactTestBitmask = Attackable 00000001
Now we need to check when an arrow hits a zombie, this is where didBeginContact
comes in
What didBeginContact
will do, is check the contactTestBitmask
of the moving item to the categoryBitmask
that it hits by using an AND operation to find a match
In our case
arrow.contactTestBitmask = 00000001
zombie.categoryMask = 00010101 AND
--------
00000001
Since our value is > 0, a contact was successful.
This means didBegins fired.
Now that we are in didBegins, we need to determine which physics body is our arrow, and which physics body is our zombie
this is where this next statement comes in
func didBegin(_ contact: SKPhysicsContact) {
// 1
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
Since arrow = 00100000 and zombie = 00010101, we know that zombie has a lower value than arrow, so in this case, zombie is < arrow.
We assign firstBody
to zombie, and secondBody
to arrow
Now we need to provide a condition.
We want to say if an undead being is hit by a burnable object, do something.
So in code this would be
if (firstBody & Undead > 0) && (secondBody & Burning > 0)
{
//burn zombie
}
But what if the arrow was a ice arrow? We do not want to go into that if statement.
Well now we can add a second condition to allow us to freeze the zombie.
if (firstBody & Undead > 0) && (secondBody & Frozen > 0)
{
//freeze zombie
}
What these ifs are doing, is making sure that the body has certain features turned on, and then perform some action in response to them.
To find out more about how bitmasks work, I would research how to do truth tables. That is essentially what this comes down to. We are just creating a few truth tables, and trying to figure out if a statement is true, and if it is true, perform an action.