ios - Swift/SpriteKit 多重碰撞检测?

标签 ios swift sprite-kit collision-detection collision

enter image description here

你好。

我有一个多重碰撞问题。 有一颗子弹击中了敌人(红色矩形)。然后,它++ 分数。 有一个螺旋(红色圆圈)应该会在敌人(红色矩形)接触到它时触发场景结束。

在这种情况下,当敌人击中螺旋时,它起作用,场景结束,我们进入菜单屏幕。 但是,当子弹击中敌人时,同样的事情发生了,我不知道为什么。

现在,这是我的代码:

struct PhysicsCategory {
    static let None : UInt32 = 0
    static let All : UInt32 = UInt32.max
    static let enemyOne : UInt32 = 0b1
    static let enemyTwo : UInt32 = 0b1
    static let bullet : UInt32 = 0b10
    static let spiral : UInt32 = 0b111
}

 spiral.physicsBody = SKPhysicsBody(rectangleOfSize: spiral.size)
        spiral.physicsBody?.categoryBitMask = PhysicsCategory.spiral
        spiral.physicsBody?.contactTestBitMask = PhysicsCategory.enemyOne
        spiral.physicsBody?.collisionBitMask = PhysicsCategory.None
...
        enemyOne.physicsBody = SKPhysicsBody(rectangleOfSize: enemyOne.size)
        enemyOne.physicsBody?.dynamic = true
        enemyOne.physicsBody?.categoryBitMask = PhysicsCategory.enemyOne
        enemyOne.physicsBody?.contactTestBitMask = PhysicsCategory.bullet | PhysicsCategory.spiral
        enemyOne.physicsBody?.collisionBitMask = PhysicsCategory.None

...

        bullet.physicsBody = SKPhysicsBody(circleOfRadius: bullet.size.width / 2)
        bullet.physicsBody?.dynamic = true
        bullet.physicsBody?.categoryBitMask = PhysicsCategory.bullet
        bullet.physicsBody?.contactTestBitMask = PhysicsCategory.enemyOne
        bullet.physicsBody?.collisionBitMask = PhysicsCategory.None
        bullet.physicsBody?.usesPreciseCollisionDetection = true

...

    func bulletDidCollideWithEnemy(bullet: SKSpriteNode, enemyOne: SKSpriteNode) {

        scoreOnScreen.text = String(score)
        score++
        bullet.removeFromParent()
        enemyOne.removeFromParent()
    }

    func enemyDidCollideWithSpiral(enemyOne: SKSpriteNode, spiral: SKSpriteNode) {

        let transition = SKTransition.revealWithDirection(SKTransitionDirection.Down, duration: 1.0)
        let skView = self.view! as SKView
        let scene = MenuScene(size: skView.bounds.size)
        scene.scaleMode = SKSceneScaleMode.AspectFill

        skView.presentScene(scene, transition: SKTransition.crossFadeWithDuration(0.5))
    }

 // Did Begin Contact
    func didBeginContact(contact: SKPhysicsContact) {
        var firstBody : SKPhysicsBody
        var secondBody : SKPhysicsBody
        var thirdBody : SKPhysicsBody
        var fourthBody : SKPhysicsBody

        if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
            firstBody = contact.bodyA
            secondBody = contact.bodyB
        } else {
            firstBody = contact.bodyB
            secondBody = contact.bodyA
        }

        if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
            thirdBody = contact.bodyA
            fourthBody = contact.bodyB
        } else {
            thirdBody = contact.bodyB
            fourthBody = contact.bodyA
        }



        if (firstBody.categoryBitMask & PhysicsCategory.enemyOne != 0) && (secondBody.categoryBitMask & PhysicsCategory.bullet != 0) {
            bulletDidCollideWithEnemy(firstBody.node as SKSpriteNode, enemyOne : secondBody.node as SKSpriteNode)
        }

        if (thirdBody.categoryBitMask & PhysicsCategory.enemyOne != 0) && (fourthBody.categoryBitMask & PhysicsCategory.spiral != 0) {
            enemyDidCollideWithSpiral(thirdBody.node as SKSpriteNode, spiral : fourthBody.node as SKSpriteNode)
        }

现在,我知道这是一团糟,但是有人可以帮助我吗?我认为问题与 bodyA.categoryBitMask 和 bodyB 被设置为不同的东西有关,即使它们是相同的(?)。 我不知道。 有人吗?

最佳答案

这里有几个问题。

  1. 您定义类别的方式使它们不易被测试。
  2. 您测试类别的方式无法获得您想要的独特答案。
  3. 您试图在一个联系人中跟踪最多四具尸体,从而混淆了您的代码。任何联系人都将始终恰好有两个主体。

让我们一次解决一个...

1。定义类别

您想要定义碰撞类别,以便游戏中的每种物体在 mask 中使用自己的位。 (您已经使用 Swift 的二进制文字表示法有了一个好主意,但您正在定义重叠的类别。)这是一个非重叠类别的示例:

struct PhysicsCategory: OptionSet {
    let rawValue: UInt32
    init(rawValue: UInt32) { self.rawValue = rawValue }

    static let enemy  = PhysicsCategory(rawValue: 0b001)
    static let bullet = PhysicsCategory(rawValue: 0b010)
    static let spiral = PhysicsCategory(rawValue: 0b100)
}

我为此使用了 Swift OptionSet 类型,因为它可以轻松地制作和测试唯一值的组合。与 enum 相比,它确实使定义我的类型及其成员的语法有点笨拙,但这也意味着我以后不必对原始值进行大量装箱和拆箱操作,尤其是如果我也制作这样的便利访问器:

extension SKPhysicsBody {
    var category: PhysicsCategory {
        get {
            return PhysicsCategory(rawValue: self.categoryBitMask)
        }
        set(newValue) {
            self.categoryBitMask = newValue.rawValue
        }
    }
}

另外,我在我的代码中使用了二进制文字符号和额外的空格和零,这样很容易确保每个类别都有自己的位——enemy 只得到最低有效位, bullet 下一个等

2 & 3. 测试和跟踪类别

我喜欢使用两层方法来联系处理程序。首先,我检查碰撞的类型——是子弹/敌人碰撞还是子弹/螺旋碰撞还是螺旋/敌人碰撞?然后,如有必要,我会检查碰撞中的哪个物体是哪个物体。这在计算方面不会花费太多,而且它使我的代码中的每一点都非常清楚发生了什么。

func didBegin(_ contact: SKPhysicsContact) {
    // Step 1. To find out what kind of contact we have,
    // construct a value representing the union of the bodies' categories
    // (same as the bitwise OR of the raw values)
    let contactCategory: PhysicsCategory = [contact.bodyA.category, contact.bodyB.category]

    if contactCategory.contains([.enemy, .bullet]) {
        // Step 2: We know it's an enemy/bullet contact, so there are only
        // two possible arrangements for which body is which:
        if contact.bodyA.category == .enemy {
            self.handleContact(enemy: contact.bodyA.node!, bullet: contact.bodyB.node!)
        } else {
            self.handleContact(enemy: contact.bodyB.node!, bullet: contact.bodyA.node!)
        }
    } else if contactCategory.contains([.enemy, .spiral]) {
        // Here we don't care which body is which, so no need to disambiguate.
        self.gameOver()

    } else if contactCategory.contains([.bullet, .spiral]) {
        print("bullet + spiral contact")
        // If we don't care about this, we don't necessarily
        // need to handle it gere. Can either omit this case,
        // or set up contactTestBitMask so that we
        // don't even get called for it.

    } else {
        // The compiler doesn't know about which possible
        // contactCategory values we consider valid, so
        // we need a default case to avoid compile error.
        // Use this as a debugging aid:
        preconditionFailure("Unexpected collision type: \(contactCategory)")
    }
}

额外学分

为什么要使用 if 语句和 OptionSet 类型的 contains() 方法?为什么不做类似这样的 switch 语句,它可以大大缩短测试值的语法?

switch contactCategory {
    case [.enemy, .bullet]:
        // ...
    case [.enemy, .spiral]:
        // ...

    // ... 

    default:
        // ...
}

这里使用 switch 的问题是它会测试你的 OptionSet 是否 equality —— 也就是说,如果 contactCategory == [.enemy, .bullet],如果是 [.enemy, .bullet, .somethingElse] 则不会触发。

使用我们在此示例中定义的联系人类别,这不是问题。但是类别/联系人位掩码系统的一个不错的功能是您可以在单个项目上编码多个类别。例如:

struct PhysicsCategory: OptionSet {
    // (don't forget rawValue and init)
    static let ship   = PhysicsCategory(rawValue: 0b0001)
    static let bullet = PhysicsCategory(rawValue: 0b0010)
    static let spiral = PhysicsCategory(rawValue: 0b0100)
    static let enemy  = PhysicsCategory(rawValue: 0b1000)
}

friendlyShip.physicsBody!.category = [.ship]
enemyShip.physicsBody!.category = [.ship, .enemy]
friendlyBullet.physicsBody!.category = [.bullet]
enemyBullet.physicsBody!.category = [.bullet, .enemy]

在这种情况下,您可能有一个类别为 [.ship, .bullet, .enemy] 的联系人 — 如果您的联系人处理逻辑专门针对 [. ship, .bullet],你会错过它的。如果您改为使用 contains,则可以测试您关心的特定标志,而无需关心是否存在其他标志。

关于ios - Swift/SpriteKit 多重碰撞检测?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26702944/

相关文章:

ios - 如何创建带删除线文本的 UILabel?

compiler-errors - swift "Ambiguous use of operator ' = ='"

ios14 Xcode 12 - 触摸 SwiftUI 对象时 Sprite 不显示,但触摸 SKScene 时可以工作

ios - 缩小我的世界 : Joints and their positions are not adjusted

ios - swift : Extra argument 'error' in call

iphone - 如何制作像iOS一样的清晰按钮

ios - UIModalPresentationFormSheet 中的 UITextView 卡住应用程序 iOS 10

iphone - 如何使用 UIViewController - IOS 在 GLKView 上显示 UIButton?

swift - 如何在整个游戏中的多个位置添加治疗节点?

ios - 在 SpriteKit 中重置场景