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

5
我仔细研究了苹果的SpriteKit和GameplayKit示例代码,并发现一个名为'DemoBots'的Swift项目。在这个项目中使用了一些非常有趣的概念,我希望将它们融入到我的项目中。我已经开始尝试将碰撞处理封装到一个处理类中,这与该示例代码处理碰撞的方式非常相似。
在这个项目中,我找到了一个名为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
    }
}

这个结构体被用于每次设置 .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中称为计算属性),例如当我将其分配给SKPhysicsBody时,它会将此添加到这些静态类字典中。但我对“mask”、“reduce”和“union”命令的解释存在问题。

那真正的作用是什么呢?

1个回答

2

collisionMask是一个计算属性,返回一个UInt32值,可以用作物理体的碰撞位掩码。如果将这个计算属性分解成其功能部分,那么它的工作原理就更容易理解。

但首先,让我们向definedCollisions字典中添加一个RPColliderType对象数组,该数组应与PlayerBot发生碰撞:

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

此时,definedCollisions 字典包含一个项目,其键为 PlayerBot,值为 [.Obstacle, .TaskBot]。可以将这些视为与 PlayerBot 可能发生碰撞的类别是 ObstacleTaskBot
现在,我们可以使用 .PlayerBot 从字典中检索值(即数组):
let array = RPColliderType.definedCollisions[.PlayerBot]

由于collisionMaskRPColliderType中被定义,因此self被用作字典键。同时,array是可选的,因为字典中可能不存在与该键对应的值。
代码然后使用reduce方法将RPColliderType对象数组合并为单个RPColliderType对象。reduce需要两个参数:一个初始值(与数组元素类型相同),以及一个接受一个值作为参数并返回一个值的函数(或闭包)。在这种情况下,初始值是一个新的RPColliderType对象,闭包的参数和返回值也是RPColliderType对象。
array?.reduce(RPColliderType(), aFunction)

苹果公司的代码使用尾随闭包而不是将函数传递给reduce。从文档中可以看出,如果您需要将闭包表达式作为函数的最后一个参数传递给函数并且闭包表达式很长,那么编写尾随闭包可能会很有用。尾随闭包是一个闭包表达式,它在支持它的函数调用的括号外(和之后)编写。reduce迭代数组,并使用初始值和每个数组元素作为参数调用闭包,并使用返回的值作为下一次迭代的初始值:
let mask = array?.reduce(RPColliderType()) {
    initial, colliderType in
    return initial.union(colliderType)
}

在这里,initial 保留了 RPColliderType 数组元素的中间合并结果,而 colliderTypearray 的当前元素。

此时,mask 是一个 RPColliderType 对象,我们可以使用以下方式将其转换为 UInt32:

mask?.rawValue

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

感谢您提供如此详细的解释。这段示例代码几乎像是您编写的一样!不过,据我理解,这是非常优雅的代码,对吧? - Simon Kemper
不用谢。这段代码是使用OptionSetType管理类别、碰撞和接触位掩码的好例子。 - 0x141E

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接