Sprite-Kit 为单个接触注册多个碰撞

Posted

技术标签:

【中文标题】Sprite-Kit 为单个接触注册多个碰撞【英文标题】:Sprite-Kit registering multiple collisions for single contact 【发布时间】:2016-09-15 07:44:50 【问题描述】:

好的 - 我确定这是重复的,Whirlwind 或 KnightOfDragons 已经发布了解决方案,但我找不到。

我正在使用 Sprite-Kit 在 Swift 中编写 Space Invader 的克隆。我遇到的问题是,当入侵者炸弹击中我的船时,代码有时会记录 2 或 3 次碰撞,立即结束游戏。这是处理炸弹/船舶碰撞的“didBeginContact”部分:

    case category.bomb.rawValue | category.ship.rawValue:
        print("Bomb hit ship!")
        let bomb = contact.bodyA.categoryBitMask == category.bomb.rawValue ? contact.bodyA.node : contact.bodyB.node
        bomb?.physicsBody?.contactTestBitMask = 0 // Seems to prevent multiple contacts with same bomb
        ship.physicsBody!.contactTestBitMask = 0 // We'll reset this after the death animation
        bomb?.removeAllActions()
        ship.removeAllActions()
        bomb?.removeFromParent()
        ships -= 1

        if ships == 0  endScene(won: false, withMessage: "A bomb got you!") 

当我运行游戏时,我看到:

Bomb hit ship!
2 ships left.

1 次炸弹击中后(这是正确的)

Bomb hit ship!
1 ships left.
Bomb hit ship!
0 ships left.

在第二次炸弹击中后(这是错误的)。

我从来没有没有注册过的联系人,有时(50%?)它工作得很好。其他时候我看到自己有-4艘船!不过,我敢肯定,我弄错了一些明显/基本的东西。

关于将炸弹和船的contactTestBitMask设置为0,我的cmets显然是错误的。我知道这不应该是必要的,因为我在接触发生时移除了炸弹,所以它不应该再次发生。

如何保证联系人只处理一次?

=================================

更新:我添加了 2 个print 语句以提供更多调试信息:

        print("bodyA is \(contact.bodyA.node!.name)")
        print("bodyB is \(contact.bodyB.node!.name)")

这是在 let bomb = contact.bodyA.category... 声明之后,现在我得到:

Bomb hit ship!
bodyA is Optional("bomb")
bodyB is Optional("playerShip")
1 ships left.

1 次炸弹击中后,并且:

Bomb hit ship!
fatal error: unexpectedly found nil while unwrapping an Optional value

在第二次炸弹击中后。所以在第二次碰撞之后,bodyA 为 nil,所以我不明白为什么 Sprite-Kit 实际上已经注册了碰撞?

有什么想法吗?

【问题讨论】:

答案是 Sprite-Kit 已将船舶和炸弹之间的多次碰撞排队等待处理(它不应该,但嘿,谁知道呢)然后 SK 为每次碰撞调用 dBC,因为我第一次拆了炸弹,第二次和以后都没有。所以我需要在投下时将炸弹标记为活动,当我检测到碰撞时检查炸弹是否处于活动状态并将其设置为非活动而不是在 dBC 中将其移除? 据我所见,当两个物体之间有多个接触点时,didBeginContact 会被多次触发。当您从纹理创建物体时,或者即使物体是圆形的,也可能会发生这种情况。目前我真的找不到自己的答案(我正在打电话),但这个解释了您遇到的崩溃:***.com/a/37003007 解决您的问题的方法很少。一种是检查 node.parent 是否为 nil,如果为 true,则从 didBeginContact 返回。另一种方式是子弹节点上的标志。可能还有其他方法,我不记得更多了。 是的,@Whirlwind 说的,记住,你真的不想在 didcontactbegin 阶段从父节点中移除,因为在某些情况下你可能需要节点仍然在场景中一秒钟接触(一颗子弹击中2个重叠的敌人,也许你想要一个鬼,谁知道)我个人喜欢保留categoryBitMask位31来处理生死,并且在物理检查期间,跳过这些节点 @Knight0fDragon 也许吧,但我不想让炸弹到处乱飞——我想让它消失。肯定只涉及 2 个节点,我无法想象幕后发生了什么。在一个测试项目中,我什至通过将 3 个精灵节点彼此叠放来尝试 3 向碰撞,dBC 仍然表现自己,被调用 3 次:一次用于 A 和 B 之间的碰撞,一次用于 B 之间的一次碰撞& C 又是 A & c 之间的那个。如果我在第一次碰撞时删除节点 A,它仍然可以正常工作... @Whirlwind - 我所看到的与您所描述的相符 - 两个身体之间的多个接触(均由纹理创建)。不确定如何在其中一个实体为 nil 的情况下调用 dBC。 【参考方案1】:

好的——看起来很简单:

        if bomb == nil return

是所有需要的。这应该添加如下:

        let bomb = contact.bodyA.categoryBitMask == category.bomb.rawValue ? contact.bodyA.node : contact.bodyB.node
        if bomb == nil return

这可以防止您在removeFromParentdidBeginContact 中的节点发生多次冲突。如果您不删除节点但仍在注册多个冲突,则使用节点的userData 属性设置某种标志,指示该节点是“活跃的”或者,子类化 SKSPriteNode 并添加自定义的“isActive”属性,这就是我为解决子弹穿过入侵者一侧并取出入侵者及其上方的问题而采取的措施。这绝不会发生在“直接命中”上。

它没有回答关于 为什么 SK 注册多个冲突的基本问题,但这确实意味着我可以删除所有关于将 contactTestBitMasks 设置为 0 的额外代码,然后返回它们的内容应该稍后等等。

编辑:因此,如果您在 didBeginContact 中删除一个节点,则该节点会被删除,但物理体不会。因此,您必须小心,因为 Sprite-Kit 似乎构建了一个已发生的物理联系人数组,然后多次调用 dBC,每个联系人一次。因此,如果您正在操作节点并在 dBC 中删除它们,请注意,如果您强制解开节点的属性,您可能会遇到问题。

【讨论】:

这对你没有帮助,你只是走运了。如果bodyA是炸弹呢?这意味着 bodyA 现在为零,这意味着您将在 let bomb = contact.bodaA.categoryBitMask 上崩溃 如果contact.bodyA.categoryBitMask == category.bomb.rawValue,我认为bomb会设置为contact.bodyA.node,否则会设置为contact.bodyB.node 例如bodyA 是炸弹。在第一遍中你杀死了炸弹,在第二遍中 bodyA 现在已经死了,所以它是 nil。您现在将在contact.bodyA.categoryBitMask == category.bomb.rawValue 崩溃,因为您正在执行nil.categoryBitMask == category.bomb.rawValue,这是不允许的,因为contact.bodyA 不是可选的。如果它是可选的,则炸弹将成为另一个精灵,这也是不受欢迎的 @Knight0fDragon 稍微回溯一下,但在接触期间肯定可以将其中一个contact.bodyX.nodes 设置为'nil',尽管'contactTestBitMask' 仍然设置。一个第一次接触两个节点都存在,但再调用 3 次 dBC,其中一个主体为“nil”,我对其进行测试并返回以防止注册多个命中。 设置为nil不是问题,你需要处理它是nil的事实

以上是关于Sprite-Kit 为单个接触注册多个碰撞的主要内容,如果未能解决你的问题,请参考以下文章

单个 SKSpriteNode 上的多个碰撞矩形

SKPhysicsJoint:接触和碰撞不起作用

使用ansys workbench 的LSDYNA模块研究碰撞的接触设置

Swift SpriteKit 基本接触/碰撞

Swift 3 Sprite 节点接触/碰撞行为不正常

当玩家节点与另一个节点碰撞时游戏结束,当玩家与边界碰撞时游戏才应该结束