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 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With