How to interpret this Swift SpriteKit example code of a physics body bitmask system

238 views Asked by At

I was taking a deep look into Apples SpriteKit & GameplayKit example code and found a Project called 'DemoBots' written in Swift. There are some very interesting concepts used in that projects which I wanted to adapt into my projects.

I was already working with encapsulating the collision-handling into a handler-class something that is very similar to the way collisions are handled in that example code.

In this project I found the following code for a struct called RPColliderType:

struct RPColliderType: OptionSetType, Hashable, CustomDebugStringConvertible {
    // MARK: Static properties

    /// A dictionary to specify which `ColliderType`s should be notified of contacts with other `ColliderType`s.
    static var requestedContactNotifications = [RPColliderType: [RPColliderType]]()

    /// A dictionary of which `ColliderType`s should collide with other `ColliderType`s.
    static var definedCollisions = [RPColliderType: [RPColliderType]]()

    // MARK: Properties

    let rawValue: UInt32

    // MARK: Options

    static var Obstacle: RPColliderType  { return self.init(rawValue: 1 << 0) }
    static var PlayerBot: RPColliderType { return self.init(rawValue: 1 << 1) }
    static var TaskBot: RPColliderType   { return self.init(rawValue: 1 << 2) }

    // MARK: Hashable

    var hashValue: Int {
        return Int(rawValue)
    }

    // MARK: SpriteKit Physics Convenience

    /// A value that can be assigned to a 'SKPhysicsBody`'s `categoryMask` property.
    var categoryMask: UInt32 {
        return rawValue
    }

    /// A value that can be assigned to a 'SKPhysicsBody`'s `collisionMask` property.
    var collisionMask: UInt32 {
        // Combine all of the collision requests for this type using a bitwise or.
        let mask = RPColliderType.definedCollisions[self]?.reduce(RPColliderType()) { initial, colliderType in
            return initial.union(colliderType)
        }

        // Provide the rawValue of the resulting mask or 0 (so the object doesn't collide with anything).
        return mask?.rawValue ?? 0
    }

    /// A value that can be assigned to a 'SKPhysicsBody`'s `contactMask` property.
    var contactMask: UInt32 {
        // Combine all of the contact requests for this type using a bitwise or.
        let mask = RPColliderType.requestedContactNotifications[self]?.reduce(RPColliderType()) { initial, colliderType in
            return initial.union(colliderType)
        }

        // Provide the rawValue of the resulting mask or 0 (so the object doesn't need contact callbacks).
        return mask?.rawValue ?? 0
    }

    // MARK: ContactNotifiableType Convenience

    /**
        Returns `true` if the `ContactNotifiableType` associated with this `ColliderType` should be
        notified of contact with the passed `ColliderType`.
    */
    func notifyOnContactWithColliderType(colliderType: RPColliderType) -> Bool {
        if let requestedContacts = RPColliderType.requestedContactNotifications[self] {
            return requestedContacts.contains(colliderType)
        }

        return false
    }
}

This struct is used every time you set the .collisionBitmask / .contactBitmask / .categoryBitmask property of an SKPhysicsBody like this: (I have implemented this using component & entity design guide)

class RPPhysicsComponent: GKComponent {

    var physicsBody: SKPhysicsBody

    init(physicsBody: SKPhysicsBody, colliderType: RPColliderType) {

        self.physicsBody = physicsBody
        self.physicsBody.categoryBitMask = colliderType.categoryMask
        self.physicsBody.collisionBitMask = colliderType.collisionMask
        self.physicsBody.contactTestBitMask = colliderType.contactMask
    }
}

So far so good. Coming from Objective-C my problem is that I do not fully understand what those following lines of code out of the RPColliderType Struct do:

/// A value that can be assigned to a 'SKPhysicsBody`'s `collisionMask` property.
var collisionMask: UInt32 {
    // Combine all of the collision requests for this type using a bitwise or.
    let mask = RPColliderType.definedCollisions[self]?.reduce(RPColliderType()) { initial, colliderType in
        return initial.union(colliderType)
    }

    // Provide the rawValue of the resulting mask or 0 (so the object doesn't collide with anything).
    return mask?.rawValue ?? 0
}

Does that mean that every time I call that computed (that's what they are called in swift, right?) property - I do this when I assign it to a SKPhysicsBody - it adds this to those static class dictionaries. But I have a problem interpreting that 'mask' / 'reduce' / 'union' commands.

What really does that do?

1

There are 1 answers

2
0x141E On BEST ANSWER

collisionMask is computed property that returns a UInt32 value that can be used as a physics body's collision bit mask. It's easier to understand how this computed property works if it is broken down into its functional parts.

But first, let's add an array of the RPColliderType objects that the PlayerBot should collide with to the definedCollisions dictionary:

RPColliderType.definedCollisions[.PlayerBot] = [.Obstacle, .TaskBot]

At this point, the definedCollisions dictionary contains a single item with PlayerBot and [.Obstacle, .TaskBot] as the key and value, respectively. Think of this as the categories that can collide with a PlayerBot are Obstacle and TaskBot.

We can now use .PlayerBot to retrieve the value (i.e., the array) from the dictionary:

let array = RPColliderType.definedCollisions[.PlayerBot]

Since collisionMask is defined in the RPColliderType, self is used as the dictionary key. Also, array is an optional since a value corresponding to the key may not exist in the dictionary.

The code then combines the array of RPColliderType objects into a single RPColliderType object using the reduce method. reduce takes two arguments: an initial value (with the same type as the elements of the array) and a function (or closure) that takes a value as an argument and returns a value. In this case, the initial value is a new RPColliderType object and the closure's argument and returned value are also RPColliderType objects:

array?.reduce(RPColliderType(), aFunction)

Apple's code uses a trailing closure instead of passing a function to reduce. From the docs,

If you need to pass a closure expression to a function as the function’s final argument and the closure expression is long, it can be useful to write it as a trailing closure instead. A trailing closure is a closure expression that is written outside of (and after) the parentheses of the function call it supports.

reduce iterates over the array and calls the closure with the initial value and each array element as arguments and the returned value is used as the initial value for the next iteration:

let mask = array?.reduce(RPColliderType()) {
    initial, colliderType in
    return initial.union(colliderType)
}

where initial keeps the intermediate union of the RPColliderType array elements and colliderType is the current element of array.

At this point, mask is an RPColliderType object that we can convert to a UInt32 with

mask?.rawValue

which is the returned value of the collisionMask computed property.