Bit masks are used by SpriteKit for several aspects such as physics and lighting. Below is an overview of how they work in the context of SpriteKit's physics engine.
Bit masks and bitwise operations determine how objects interact with each other. In SpriteKit, there are two types of interactions between physics bodies:
-
Contact: a signal when two bodies overlap, without modifying their physical behavior.
-
Collision: a physical interaction that applies forces and prevents bodies from passing through each other.
A SKPhysicsBody has four properties that take a bit mask value:
let body = SKPhysicsBody(circleOfRadius: 30)
body.categoryBitMask = // The category of this body
body.contactTestBitMask = // Categories this body should notify contacts with
body.collisionBitMask = // Categories this body should collide with
body.fieldBitMask = // Categories of field nodes this body is affected by
A bit mask value or a bitwise operation on multiple bit mask values can be passed to each of these properties.
// First, store some categories in a data structure for ease of use
struct BitMasks {
static let bodyLayer1: UInt32 = 1 << 0
static let bodyLayer2: UInt32 = 1 << 1
static let bodyLayer3: UInt32 = 1 << 2
static let bodyLayer4: UInt32 = 1 << 3
}
struct FieldBitMasks {
static let fieldLayer1: UInt32 = 1 << 0
static let fieldLayer2: UInt32 = 1 << 1
}
// Assign categories
bodyA.categoryBitMask = BitMasks.bodyLayer1
bodyB.categoryBitMask = BitMasks.bodyLayer2
bodyC.categoryBitMask = BitMasks.bodyLayer3
// Define contacts
// bodyA should detect contacts with both layer2 and layer3
// Notice the bitwise operation OR written `|`
bodyA.contactTestBitMask = BitMasks.bodyLayer2 | BitMasks.bodyLayer3
// Define collisions
// bodyA should collide with layer2 and layer4, but not layer3
bodyA.collisionBitMask = BitMasks.bodyLayer2 | BitMasks.bodyLayer4
// Define fields
// bodyB will be affected by field nodes of this category
bodyB.fieldBitMask = FieldBitMasks.fieldLayer1
SpriteKit bit masks use the UInt32 type. There are multiple ways of representing a UInt32:
// bitwise shift notation
let bitmask: UInt32 = 1 << 31
// decimal
let bitmask: UInt32 = 2147483648
// hexadecimal
let bitmask: UInt32 = 0x80000000
// binary (Swift uses the prefix `0b` to indicate binary numbers)
let bitmask: UInt32 = 0b10000000000000000000000000000000
A UInt32 has 32 bits, each of which can be turned on or off independently. We can define 32 individual flags, ranging from 1 << 0 to 1 << 31. Notice the visual pattern of the shifting bits in the full list:
let bitMask00: UInt32 = 1 << 0 // 0b00000000000000000000000000000001
let bitMask01: UInt32 = 1 << 1 // 0b00000000000000000000000000000010
let bitMask02: UInt32 = 1 << 2 // 0b00000000000000000000000000000100
let bitMask03: UInt32 = 1 << 3 // 0b00000000000000000000000000001000
let bitMask04: UInt32 = 1 << 4 // 0b00000000000000000000000000010000
let bitMask05: UInt32 = 1 << 5 // 0b00000000000000000000000000100000
let bitMask06: UInt32 = 1 << 6 // 0b00000000000000000000000001000000
let bitMask07: UInt32 = 1 << 7 // 0b00000000000000000000000010000000
let bitMask08: UInt32 = 1 << 8 // 0b00000000000000000000000100000000
let bitMask09: UInt32 = 1 << 9 // 0b00000000000000000000001000000000
let bitMask10: UInt32 = 1 << 10 // 0b00000000000000000000010000000000
let bitMask11: UInt32 = 1 << 11 // 0b00000000000000000000100000000000
let bitMask12: UInt32 = 1 << 12 // 0b00000000000000000001000000000000
let bitMask13: UInt32 = 1 << 13 // 0b00000000000000000010000000000000
let bitMask14: UInt32 = 1 << 14 // 0b00000000000000000100000000000000
let bitMask15: UInt32 = 1 << 15 // 0b00000000000000001000000000000000
let bitMask16: UInt32 = 1 << 16 // 0b00000000000000010000000000000000
let bitMask17: UInt32 = 1 << 17 // 0b00000000000000100000000000000000
let bitMask18: UInt32 = 1 << 18 // 0b00000000000001000000000000000000
let bitMask19: UInt32 = 1 << 19 // 0b00000000000010000000000000000000
let bitMask20: UInt32 = 1 << 20 // 0b00000000000100000000000000000000
let bitMask21: UInt32 = 1 << 21 // 0b00000000001000000000000000000000
let bitMask22: UInt32 = 1 << 22 // 0b00000000010000000000000000000000
let bitMask23: UInt32 = 1 << 23 // 0b00000000100000000000000000000000
let bitMask24: UInt32 = 1 << 24 // 0b00000001000000000000000000000000
let bitMask25: UInt32 = 1 << 25 // 0b00000010000000000000000000000000
let bitMask26: UInt32 = 1 << 26 // 0b00000100000000000000000000000000
let bitMask27: UInt32 = 1 << 27 // 0b00001000000000000000000000000000
let bitMask28: UInt32 = 1 << 28 // 0b00010000000000000000000000000000
let bitMask29: UInt32 = 1 << 29 // 0b00100000000000000000000000000000
let bitMask30: UInt32 = 1 << 30 // 0b01000000000000000000000000000000
let bitMask31: UInt32 = 1 << 31 // 0b10000000000000000000000000000000
Properties that take a bit mask can take one of these flags, or any combination of them. Each individual flag is just a handy building block. We can combine several flags using the bitwise OR operator |, giving us 2^32 possible mask values.
Two combinations are particularly useful: all flags turned on, and no flags turned on:
// Interact with all bitmasks (0b11111111111111111111111111111111)
let all: UInt32 = UInt32.max
// Interact with nothing (0b00000000000000000000000000000000)
let none: UInt32 = 0
Note that in SpriteKit physics, physics bodies and physics fields use bit masks separately. The assignments for bodies and fields are independent, so the same bit can mean something different in each.
Links
-
Apple Documentation, UInt32.
-
Apple Documentation, SKPhysicsBody.categoryBitMask.