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...
1. Defining Categories
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:
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 anenum
, 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: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.2 & 3. Testing & Tracking Categories
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.
Extra Credit
Why use
if
statements and theOptionSet
type'scontains()
method? Why not do something like thisswitch
statement, which makes the syntax for testing values a lot shorter?The problem with using
switch
here is that it tests yourOptionSet
s for equality — that is, case #1 fires ifcontactCategory == [.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:
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 usecontains
instead, you can test for the specific flags you care about without needing to care whether other flags are present.