如何解释这个 Swift 物理体位掩码系统的 SpriteKit 示例代码

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

我深入研究了 Apples SpriteKit 和 GameplayKit 示例代码,发现了一个用 Swift 编写的名为 'DemoBots' 的项目。该项目中使用了一些非常有趣的概念,我想将它们应用到我的项目中。

我已经在将碰撞处理封装到处理程序中 -class 这与示例代码中处理碰撞的方式非常相似。

在这个项目中,我找到了名为 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
    }
}

每次设置 SKPhysicsBody.collisionBitmask / .contactBitmask / .categoryBitmask 属性 时都会使用此结构:(我已经实现了这使用组件和实体设计指南)

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

到目前为止一切顺利。来自 Objective-C 我的问题是我不完全理解 RPColliderType 结构中以下代码行的作用:

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

这是否意味着每次我调用计算(这就是它们在 swift 中的调用,对吗?)属性 - 我在将它分配给 [=18= 时这样做] - 它将此添加到那些静态 class 词典中。但是我在解释“mask”/“reduce”/“union”命令时遇到问题。

这到底有什么作用?

collisionMask 计算 属性 returns 一个 UInt32 值,可以用作物理体的碰撞位掩码。如果将此计算的 属性 分解为功能部分,则更容易理解其工作原理。

但首先,让我们将 PlayerBot 应该与之碰撞的 RPColliderType 对象的数组添加到 definedCollisions 字典中:

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

此时,definedCollisions 字典包含一个项目,其中 PlayerBot[.Obstacle, .TaskBot] 分别作为键和值。将此视为可能与 PlayerBot 冲突的类别是 ObstacleTaskBot.

我们现在可以使用 .PlayerBot 从字典中检索值(即数组):

let array = RPColliderType.definedCollisions[.PlayerBot]

由于 collisionMask 定义在 RPColliderType 中,因此 self 用作字典键。此外,array 是可选的,因为字典中可能不存在对应于该键的值。

代码然后使用 reduce 方法将 RPColliderType 个对象的数组合并为一个 RPColliderType 对象。 reduce 接受两个参数:一个初始值(与数组元素具有相同的类型)和一个函数(或闭包),该函数(或闭包)接受一个值作为参数和 returns 一个值。在这种情况下,初始值是一个新的 RPColliderType 对象,闭包的参数和返回值也是 RPColliderType 个对象:

array?.reduce(RPColliderType(), aFunction)

Apple 的代码使用尾随闭包而不是将函数传递给 reduce。从文档中,

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遍历数组并以初始值和每个数组元素作为参数调用闭包,返回值用作下一次迭代的初始值:

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

其中 initial 保留 RPColliderType 数组元素的中间并集,colliderTypearray 的当前元素。

此时,mask 是一个 RPColliderType 对象,我们可以使用

将其转换为 UInt32
mask?.rawValue

这是 collisionMask 计算的 属性 的返回值。