Hello.
I have a multiple collision problem. There is a bullet, which hits the enemy(red rectangle). Then, it ++ the score. There is a spiral (red circle) which is supossed to trigger the scene to end when the enemy (red rectangle) touches it.
In this situation, when enemy hits the spiral, it works, the scene ends, and we go to the menu screen. But, when bullet hits the enemy, the same thing happens, and I don't know why.
Now, here's 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 it's a mess, but can anyone help me? I think it the problem has to do with the bodyA.categoryBitMask and bodyB being set to different things even thought they are the same(?). I don't know. Anyone?
Several problems here.
Let's solve them one at a time...
You want to define collision categories so that each kind of body in your game uses its own bit in the mask. (You've got a good idea using Swift's binary literal notation, but you're defining categories that overlap.) Here's 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'm using a Swift OptionSet
type for this, because it makes it easy to make and test for combinations of unique values. It does make the syntax for defining my type and its members a bit unwieldy compared to an enum
, but it also means I don't have to do a lot of boxing and unboxing raw values later, especially if I also make convenience accessors like this one:
extension SKPhysicsBody {
var category: PhysicsCategory {
get {
return PhysicsCategory(rawValue: self.categoryBitMask)
}
set(newValue) {
self.categoryBitMask = newValue.rawValue
}
}
}
Also, I'm using the binary literal notation and extra whitespace and zeroes in my code so that it's easy to make sure that each category gets its own bit — enemy
gets only the least significant bit, bullet
the next one, etc.
I like to use a two-tiered approach to contact handlers. First, I check for the kind of collision — is it a bullet/enemy collision or a bullet/spiral collision or a spiral/enemy collision? Then, if necessary I check to see which body in the collision is which. This doesn't cost much in terms of computation, and it makes it very clear at every point in my code what's going on.
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)")
}
}
Why use if
statements and the OptionSet
type's contains()
method? Why not do something like this switch
statement, which makes the syntax for testing values a lot shorter?
switch contactCategory {
case [.enemy, .bullet]:
// ...
case [.enemy, .spiral]:
// ...
// ...
default:
// ...
}
The problem with using switch
here is that it tests your OptionSet
s for equality — that is, case #1 fires if contactCategory == [.enemy, .bullet]
, and won't fire if it's [.enemy, .bullet, .somethingElse]
.
With the contact categories we've defined in this example, that's not a problem. But one of the nice features of the category/contact bit mask system is that you can encode multiple categories on a single item. For example:
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 a situation like that, you could have a contact whose category is [.ship, .bullet, .enemy]
— and if your contact handling logic is testing specifically for [.ship, .bullet]
, you'll miss it. If you use contains
instead, you can test for the specific flags you care about without needing to care whether other flags are present.