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?
collisionMask
is computed property that returns aUInt32
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 thePlayerBot
should collide with to thedefinedCollisions
dictionary:At this point, the
definedCollisions
dictionary contains a single item withPlayerBot
and[.Obstacle, .TaskBot]
as the key and value, respectively. Think of this as the categories that can collide with aPlayerBot
areObstacle
andTaskBot
.We can now use
.PlayerBot
to retrieve the value (i.e., the array) from the dictionary:Since
collisionMask
is defined in theRPColliderType
,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 singleRPColliderType
object using thereduce
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 newRPColliderType
object and the closure's argument and returned value are alsoRPColliderType
objects:Apple's code uses a trailing closure instead of passing a function to
reduce
. From the docs,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:where
initial
keeps the intermediate union of theRPColliderType
array elements andcolliderType
is the current element ofarray
.At this point,
mask
is anRPColliderType
object that we can convert to aUInt32
withwhich is the returned value of the
collisionMask
computed property.