Swift / SpriteKit多重碰撞检测?
你好。
我有一个多重碰撞问题。 有一颗子弹击中敌人(红色矩形)。 然后,它++的分数。 有一个螺旋形(红色的圆圈),当敌人(红色矩形)触及它时触发场景结束。
在这种情况下,当敌人撞上螺旋状时,它就起作用,场景结束,我们进入菜单屏幕。 但是,当子弹击中敌人,同样的事情发生,我不知道为什么。
现在,这是我的代码:
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被设置为不同的东西,甚至认为它们是相同的(?)。 我不知道。 任何人?
这里有几个问题。
- 您正在以某种方式定义类别,以防止它们被轻松testing。
- 您正在以某种方式testing类别,而这种方式并不能为您提供所需的唯一答案。
- 通过尝试在一个联系人中跟踪最多四个主体,您已经混淆了您的代码。 任何联系人总是会有两个正文。
我们一次解决一个问题
1.定义类别
你想定义碰撞类别,以便游戏中的每一种物体都在掩码中使用自己的位。 (使用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
types,因为它使得可以轻松地进行和testing独特值的组合。 它确实使得我的types及其成员的定义与enum
相比有些笨拙,但这也意味着我不必在后面做很多装箱和拆箱的原始值,特别是如果我也使这样的便利访问一:
extension SKPhysicsBody { var category: PhysicsCategory { get { return PhysicsCategory(rawValue: self.categoryBitMask) } set(newValue) { self.categoryBitMask = newValue.rawValue } } }
另外,我在代码中使用了二进制文字符号和额外的空白和零,这样就很容易确保每个类别都有自己的位 – enemy
只获得最小的位,下bullet
是bullet
等等。
2&3.testing和跟踪类别
我喜欢用两层的方式联系处理程序。 首先,我要检查碰撞的种类 – 是子弹/敌人的碰撞还是子弹/螺旋碰撞,还是螺旋/敌人的碰撞? 然后,如果有必要的话,我会检查一下碰撞中的哪一个是哪个。 这在计算方面没有多less成本,并且在代码中的每一点上都非常清楚发生了什么。
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
types的contains()
方法? 为什么不做这样的switch
语句,这使得testing值的语法更短?
switch contactCategory { case [.enemy, .bullet]: // ... case [.enemy, .spiral]: // ... // ... default: // ... }
这里使用switch
的问题是,它testing你的OptionSet
的相等性 – 也就是说,如果contactCategory == [.enemy, .bullet]
,情况#1会触发,如果它是[.enemy, .bullet, .somethingElse]
。
通过我们在这个例子中定义的联系人类别,这不是一个问题。 但类别/联系人位掩码系统的一个很好的function是,您可以在一个项目上编码多个类别。 例如:
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
,则可以testing您关心的特定标志,而不必关心其他标志是否存在。