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?
collisionMaskis computed property that returns aUInt32value 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
RPColliderTypeobjects that thePlayerBotshould collide with to thedefinedCollisionsdictionary:At this point, the
definedCollisionsdictionary contains a single item withPlayerBotand[.Obstacle, .TaskBot]as the key and value, respectively. Think of this as the categories that can collide with aPlayerBotareObstacleandTaskBot.We can now use
.PlayerBotto retrieve the value (i.e., the array) from the dictionary:Since
collisionMaskis defined in theRPColliderType,selfis used as the dictionary key. Also,arrayis an optional since a value corresponding to the key may not exist in the dictionary.The code then combines the array of
RPColliderTypeobjects into a singleRPColliderTypeobject using thereducemethod.reducetakes 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 newRPColliderTypeobject and the closure's argument and returned value are alsoRPColliderTypeobjects:Apple's code uses a trailing closure instead of passing a function to
reduce. From the docs,reduceiterates 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:where
initialkeeps the intermediate union of theRPColliderTypearray elements andcolliderTypeis the current element ofarray.At this point,
maskis anRPColliderTypeobject that we can convert to aUInt32withwhich is the returned value of the
collisionMaskcomputed property.