Fast / SpriteKit multiple collision detection?

enter image description here

Hello.

I have a problem with multiple collisions. There is a bullet that hits the enemy (red rectangle). Then this is a + rating. There is a spiral (red circle) that slouches to stop the scene when the enemy (red rectangle) touches it.

In this situation, when the enemy falls into a spiral, he works, the scene ends, and we go to the menu screen. But when the bullet hits the enemy, the same thing happens, and I do not know why.

Now, here is my code:

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) } 

Now I know this is a mess, but can anyone help me? I think the problem is that bodyA.categoryBitMask and bodyB are set to different things, even if they are considered the same (?). I dont know. Is anyone

+6
source share
1 answer

A few problems here.

  • You define categories so that they are not easily tested.
  • You test categories so that you donโ€™t get the unique answers you want.
  • You messed up your code trying to track up to four bodies in one contact. Any contact will always have exactly two bodies.

Allow them one at a time ...

1. Definition of categories

You want to define collision categories so that each body type in your game uses its own mask bit. (You have a good idea using Swift binary literals, but you define categories that overlap.) Here is an example of non-overlapping categories:

 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) } 

I use the Swift OptionSet type for this because it makes it easy to create and test combinations of unique values. This makes the syntax for defining my type and its members a bit cumbersome compared to enum , but it also means that I don't need to do a lot of boxing and unpack the raw values โ€‹โ€‹later, especially if I also make convenient accessors like this:

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

In addition, I use binary literature and extra spaces and zeros in my code to make it easy to make sure that each category gets its own bit - the enemy gets only the least significant bit, the bullet next, etc.

2 and 3. Categories of testing and tracking

I like to use a two-tier approach to contact handlers. First of all, I check which collision is a bullet / enemy collision or a bullet / spiral collision or a spiral / enemy collision? Then, if necessary, I will check which body is in the collision. This is not computationally expensive, and in every part of my code it is very clear what is happening.

 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 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)") } } 

Additional loan

Why use if and the OptionSet method of type contains() ? Why not make something like this switch , which makes the syntax for testing values โ€‹โ€‹much shorter?

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

The problem with using switch is that it checks your OptionSet for equality - that is, case # 1 is triggered if contactCategory == [.enemy, .bullet] , and will not fail if it is [.enemy, .bullet, .somethingElse] .

With the contact categories that we defined in this example, this is not a problem. But one of the nice features of the category bit / contact bit system is that you can encode multiple categories for one item. For instance:

 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] 

In such a situation, you may have a contact whose category is [.ship, .bullet, .enemy] - and if your contact processing logic is tested specifically for [.ship, .bullet] , you will skip it. If you use contains instead, you can test your flags without worrying about having other flags.

+30
source

Source: https://habr.com/ru/post/977617/


All Articles