red character 'A' without the horizontal bar Achraf Kassioui

SpriteKit Bit Masks

Blog / Apple Dev,

Bit masks are used to tag objects and define which objects can interact with each other.

Bit masks and bitwise operations 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 physics engine.

In physics, bit masks and bitwise operations determine how bodies interact with each others. There are two types of physics-based interactions:

Each physics body has 4 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

Bitwise operations can be used for each of these properties:

// 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
}

// Define categories
bodyA.categoryBitMask = BitMasks.bodyLayer1
bodyB.categoryBitMask = BitMasks.bodyLayer2
bodyC.categoryBitMask = BitMasks.bodyLayer3

// Define contacts
// bodyA should detect contacts with both layer2 and layer3
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

Notice how a bit mask properties accept logical operations such as |.

Bit masks in SpriteKit 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

We can assign 32 individual values to a categoryBitMask, ranging from 1 << 0 to 1 << 31. Here’s the full list:

let categoryBitMask0:  UInt32 = 1 << 0   // 0b00000000000000000000000000000001
let categoryBitMask1:  UInt32 = 1 << 1   // 0b00000000000000000000000000000010
let categoryBitMask2:  UInt32 = 1 << 2   // 0b00000000000000000000000000000100
let categoryBitMask3:  UInt32 = 1 << 3   // 0b00000000000000000000000000001000
let categoryBitMask4:  UInt32 = 1 << 4   // 0b00000000000000000000000000010000
let categoryBitMask5:  UInt32 = 1 << 5   // 0b00000000000000000000000000100000
let categoryBitMask6:  UInt32 = 1 << 6   // 0b00000000000000000000000001000000
let categoryBitMask7:  UInt32 = 1 << 7   // 0b00000000000000000000000010000000
let categoryBitMask8:  UInt32 = 1 << 8   // 0b00000000000000000000000100000000
let categoryBitMask9:  UInt32 = 1 << 9   // 0b00000000000000000000001000000000
let categoryBitMask10: UInt32 = 1 << 10  // 0b00000000000000000000010000000000
let categoryBitMask11: UInt32 = 1 << 11  // 0b00000000000000000000100000000000
let categoryBitMask12: UInt32 = 1 << 12  // 0b00000000000000000001000000000000
let categoryBitMask13: UInt32 = 1 << 13  // 0b00000000000000000010000000000000
let categoryBitMask14: UInt32 = 1 << 14  // 0b00000000000000000100000000000000
let categoryBitMask15: UInt32 = 1 << 15  // 0b00000000000000001000000000000000
let categoryBitMask16: UInt32 = 1 << 16  // 0b00000000000000010000000000000000
let categoryBitMask17: UInt32 = 1 << 17  // 0b00000000000000100000000000000000
let categoryBitMask18: UInt32 = 1 << 18  // 0b00000000000001000000000000000000
let categoryBitMask19: UInt32 = 1 << 19  // 0b00000000000010000000000000000000
let categoryBitMask20: UInt32 = 1 << 20  // 0b00000000000100000000000000000000
let categoryBitMask21: UInt32 = 1 << 21  // 0b00000000001000000000000000000000
let categoryBitMask22: UInt32 = 1 << 22  // 0b00000000010000000000000000000000
let categoryBitMask23: UInt32 = 1 << 23  // 0b00000000100000000000000000000000
let categoryBitMask24: UInt32 = 1 << 24  // 0b00000001000000000000000000000000
let categoryBitMask25: UInt32 = 1 << 25  // 0b00000010000000000000000000000000
let categoryBitMask26: UInt32 = 1 << 26  // 0b00000100000000000000000000000000
let categoryBitMask27: UInt32 = 1 << 27  // 0b00001000000000000000000000000000
let categoryBitMask28: UInt32 = 1 << 28  // 0b00010000000000000000000000000000
let categoryBitMask29: UInt32 = 1 << 29  // 0b00100000000000000000000000000000
let categoryBitMask30: UInt32 = 1 << 30  // 0b01000000000000000000000000000000
let categoryBitMask31: UInt32 = 1 << 31  // 0b10000000000000000000000000000000

These values can be freely combined using bitwise OR | for categoryBitMask, contactTestBitMask, collisionBitMask, and fieldBitMask.

In SpriteKit physics, there are two independent groups of category bit masks, each allowing up to 32 individual categories:

In addition to the 32 distinct values, there are two other useful values, "all" and "none":

// Interact with all bitmasks (0b11111111111111111111111111111111)
let all: UInt32 = UInt32.max

// Interact with nothing (0b00000000000000000000000000000000)
let none: UInt32 = 0

The important bit about bit masks is that they are tags, not boxes. An object can belong to multiple categories by assigning a bitwise operation to its own category bit mask property.