SKPhysicsContact 崩溃的扩展

extension for SKPhysicsContact crashing

提问人:Fayyouz 提问时间:7/18/2017 最后编辑:CommunityFayyouz 更新时间:7/18/2017 访问量:82

问:

我正在用 SpriteKit 创建一个游戏,它在 2 个身体之间发生碰撞。设置好身体后,我实现了moetod,如下所示:didBegin(_contact:)

func didBegin(_ contact: SKPhysicsContact) {
    if contact.bodyA.categoryBitMask == 0 && contact.bodyB.categoryBitMask == 1 {
        gameOver()
    }
}

它工作得很好。

后来,在检查此方法的文档时,我发现了以下内容:

接触参数中描述的两个物理实体不按保证顺序传递

因此,为了安全起见,我使用一个函数扩展了该类,该函数在两个主体之间交换 categoryBitMask,如下所示:SKPhysicsContact

extension SKPhysicsContact {

    func bodiesAreFromCategories(_ a: UInt32, and b: UInt32) -> Bool {
        if self.bodyA.categoryBitMask == a && self.bodyB.categoryBitMask == b { return true }
        if self.bodyA.categoryBitMask == b && self.bodyB.categoryBitMask == a { return true }
        return false
    }
}

问题是,当调用函数时,应用程序崩溃,并且出现以下错误:

2017-07-18 13:44:18.548 iSnake Retro[17606:735367] -[PKPhysicsContact bodiesAreFromCategories:and:]:无法识别的选择器发送到实例0x60000028b950

2017-07-18 13:44:18.563 iSnake Retro[17606:735367] *** 由于未捕获的异常“NSInvalidArgumentException”而终止应用程序,原因:“-[PKPhysicsContact bodiesAreFromCategories:and:]:无法识别的选择器发送到实例0x60000028b950”

ios swift sprite-kit 崩溃 skphysicscontact

评论

0赞 Fluidity 7/18/2017
这是一个很酷的扩展,但通常您只需将类别掩码添加在一起即可完全避免此问题。
0赞 Fayyouz 7/18/2017
@Fluidity 你的意思是用 ?如果是,这是不可能的,因为同一类别的 2 个主体之间的碰撞会产生与 2 个不同类别的主体不同的动作0 | 1
1赞 Fluidity 7/18/2017
不,看我的答案。将类别掩码相加以获得唯一的总和。您必须将面具设置为2的幂才能起作用。2+2 总是等于 4,而 2 + 4 总是等于 6,即使它是 4 + 2
1赞 Fluidity 7/18/2017
您不应该使用 0 作为正文,因为这会为您提供非唯一联系人,例如 0+4 是 4,但 2+2 也是如此
2赞 Whirlwind 7/18/2017
关于这一切的附加信息: forums.developer.apple.com/thread/24545

答:

2赞 Knight0fDragon 7/18/2017 #1

SKPhysicsContact是一个包装类,你正在扩展,但实际上你需要扩展(你不能这样做)PKPhysicsContactSKPhysicsContactPKPhysicsContact

为了保持联系方式的秩序,只需执行以下操作:

let bodyA = contact.bodyA.categoryBitMask <= self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB

let bodyB = contact.bodyA.categoryBitMask > self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB

这样,当您需要检查特定节点时,您就知道要命中哪个节点,因此

func didBegin(_ contact: SKPhysicsContact) {
    if contact.bodyA.categoryBitMask == 0 && contact.bodyB.categoryBitMask == 1 {
        gameOver()
    }
}

成为

func didBegin(_ contact: SKPhysicsContact) {

    let bodyA = contact.bodyA.categoryBitMask <= self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB

    let bodyB = contact.bodyA.categoryBitMask > self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
    if bodyA.categoryBitMask == 0 && bodyB.categoryBitMask == 1 {
        gameOver()
    }
}

然后,您可以添加到代码中,因为您现在知道了各个主体。

func didBegin(_ contact: SKPhysicsContact) {

    let bodyA = contact.bodyA.categoryBitMask <= self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB

    let bodyB = contact.bodyA.categoryBitMask > self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
    if bodyA.categoryBitMask == 0 && bodyB.categoryBitMask == 1 {
        gameOver()
        //since I know bodyB is 1,  let's add an emitter effect on bodyB.node
    }
}

顺便说一句,对于看到这个答案的人来说,categoryBitMask 0 不应该触发任何联系人,你需要某种值才能工作。这是一个超出作者问题范围的错误,所以我将其保留在 0 和 1 处,因为这是他/她的代码正在做的事情,并且他声称它有效。

评论

0赞 Fluidity 7/19/2017
现在有了额外的信息,这(和文献)更有意义了
0赞 Knight0fDragon 7/19/2017
@Fluidity,当你不明白某些东西时,只需添加评论,我会详细说明
0赞 Fluidity 7/19/2017
是的,我们甚至聊了几次。
0赞 Fluidity 7/19/2017
我认为我需要的关键是“我们将根据最低位掩码到最高位掩码进行排序,因此我们将创建一个元组 (x, y),并且 x 将始终是最低的类别。在这个例子中,这一点更加明显
0赞 Knight0fDragon 7/19/2017
好吧,在答案上添加评论。与其只是对LOL投反对票,不如给我一个机会来改进我的答案
2赞 Fluidity 7/18/2017 #2

这显然是一个错误,正如这里回答的那样:https://stackoverflow.com/a/33423409/6593818

问题是,联系人的类型是 PKPhysicsContact(正如您已经注意到的),即使您明确告诉它是 SKPhysicsContact,并且扩展位于 SKPhysicsContact 上。您必须能够对 PKPhysicsContact 进行扩展才能正常工作。从这个逻辑中,我们可以说目前没有实例方法在SKPhysicsContact扩展中起作用。我会说这是 SpriteKit 的一个错误,你应该提交一个雷达。类方法仍然有效,因为您在类本身上调用它们。

同时,您应该能够将该方法移动到您的场景或其他对象中,并在那里成功调用它。

郑重声明,这不是 Swift 特有的问题。如果你在 SKPhysicsContact 的 Objective-C 类别中使用相同的方法,你会得到同样的崩溃。

您可以向 Apple 提交错误报告:

https://developer.apple.com/bug-reporting/

并向社区报告:

https://openradar.appspot.com/search?query=spritekit

但是,您真正想对代码执行的操作是将类别掩码添加在一起。然后检查总和(2 + 4 和 4 + 2 始终等于 6,无论 bodyA 和 bodyB 的顺序如何)。

如果您以 2 的幂(2、4、8、16 等)正确设置口罩,这就是您获得唯一联系人的方式

评论

0赞 Knight0fDragon 7/18/2017
这不是一个错误
0赞 Knight0fDragon 7/18/2017
将它们加在一起也是一个可怕的主意,你仍然不知道哪个身体是哪个,所以没有办法保证身体的顺序
0赞 Fluidity 7/18/2017
@Knight0fDragon这是一个错误。由于苹果糟糕的设计决策,因此破坏了应该有效的代码。这就像试图吃一个苹果,然后意识到它是塑料的,而其他所有水果都是可以食用的。塑料苹果需要“固定”
0赞 Fluidity 7/18/2017
@Knight0fDragon我的加法想法也很完美,因为他不在乎保持身体秩序,只有当有独特的碰撞导致游戏结束:)
1赞 Knight0fDragon 7/18/2017
不,所有 SKPhysics 类都是包装类,这是它们的预期用途,并且是设计使然。你不应该扩展它。错误是指非设计使然的事情发生。您提出的只是一种适用于这种情况的变通方法,这意味着它不能扩展到其他项目。最后:他在他的担忧中提到了他试图解决的问题。你的方式与他原来的方式没有什么不同,只是检查bodyA的0或1,而不是两个身体。The two bodies described in the contact parameter are not passed in a guaranteed order.