I'm trying to make a simple Space Shooter game. The contact should happen either between the torpedo and the alien or the shuttle and the alien. The problem is that this second contact (shuttle vs. alien) only happens after the first kind of contact has happend (torpedo vs. alien) and further more they're not always precise. This is a struct created outside the class
struct PhysicsCategory {
static let alien : UInt32 = 1
static let torpedo : UInt32 = 2
static let shuttle : UInt32 = 3 }
Shuttle:
shuttle.physicsBody = SKPhysicsBody(rectangleOfSize: shuttle.size)
shuttle.physicsBody?.categoryBitMask = PhysicsCategory.shuttle
shuttle.physicsBody?.contactTestBitMask = PhysicsCategory.alien
shuttle.physicsBody?.dynamic = false
shuttle.physicsBody?.affectedByGravity = false
Torpedo:
torpedo.physicsBody = SKPhysicsBody(rectangleOfSize: torpedo.size)
torpedo.physicsBody?.categoryBitMask = PhysicsCategory.torpedo
torpedo.physicsBody?.contactTestBitMask = PhysicsCategory.alien
torpedo.physicsBody?.affectedByGravity = false
torpedo.physicsBody?.dynamic = false
Alien:
alien.physicsBody = SKPhysicsBody(rectangleOfSize: torpedo.size)
alien.physicsBody?.categoryBitMask = PhysicsCategory.alien
alien.physicsBody?.contactTestBitMask = PhysicsCategory.torpedo
alien.physicsBody?.affectedByGravity = false
alien.physicsBody?.dynamic = true
Finally, here's my contact code:
func didBeginContact(contact: SKPhysicsContact) {
var firstBody : SKPhysicsBody = contact.bodyA
var secondBody : SKPhysicsBody = contact.bodyB
if ((firstBody.categoryBitMask == PhysicsCategory.alien) && (secondBody.categoryBitMask == PhysicsCategory.torpedo)) ||
((firstBody.categoryBitMask == PhysicsCategory.torpedo) && (secondBody.categoryBitMask == PhysicsCategory.alien)) {
self.contactWithTorpedo(firstBody.node as! SKSpriteNode, torpedo: secondBody.node as! SKSpriteNode)
} else if ((firstBody.categoryBitMask == PhysicsCategory.shuttle) && (secondBody.categoryBitMask == PhysicsCategory.alien)) {
self.contactWithShuttle(firstBody.node as! SKSpriteNode, shuttle: secondBody.node as! SKSpriteNode)
}
}
func contactWithTorpedo (alien: SKSpriteNode, torpedo : SKSpriteNode) {
alien.removeFromParent()
torpedo.removeFromParent()
score++
scoreLabel.text = "score: " + "\(score)"
}
func contactWithShuttle (alien:SKSpriteNode, shuttle:SKSpriteNode) {
alien.removeFromParent()
shuttle.removeFromParent()
self.view?.presentScene(EndScene())
}
I'm not really sure where the problem is, plus I've seen a couple of tutorials do the same. I don't know if it's relevant by the way, but this is not an iOS game but an OSX. Thank you in advance!
You might find it less confusing to restructure your didBeginContact
as follows, as this avoids the firstBody/secondbody stuff and the complicated if...then
conditions to see what has contacted what:
func didBeginContact(contact: SKPhysicsContact) {
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch contactMask {
case PhysicsCategory.alien | PhysicsCategory.torpedo:
// alien and torpedo have contacted
contact.bodyA.removeFromParent()
contact.bodyB.removeFromParent()
score += 1
scoreLabel.text = "score: " + "\(score)"
case PhysicsCategory.alien | PhysicsCategory.shuttle:
// alien and shuttle have contacted
contact.bodyA.removeFromParent()
contact.bodyB.removeFromParent()
self.view?.presentScene(EndScene())
default :
//Some other contact has occurred
print("Some other contact")
}
}
You can just add as many PhysicsCategory.enemy | PhysicsCategory.player
cases as you need for all the contacts that you have to take action for in your game. Code each potential contact individually and you won't loose yourself in if...then...else.
if you do need to reference only one of the nodes involved in a contact, (e.g. to remove the player after an enemy hits it), you can do it like this:
let playerNode = contact.bodyA.categoryBitMask == PhysicsCategory.player ? contact.bodyA.node! : contact.bodyB.node!
playernode.removefromParent
I would recommend you to read the the docs about SKPhysicsBody.
Every physics body in a scene can be assigned to up to 32 different categories, each corresponding to a bit in the bit mask. You define the mask values used in your game. In conjunction with the collisionBitMask and contactTestBitMask properties, you define which physics bodies interact with each other and when your game is notified of these interactions
First of all I would change the PhysicsCategory to
struct PhysicsCategory {
static let alien : UInt32 = 0x1 << 1
static let torpedo : UInt32 = 0x1 << 2
static let shuttle : UInt32 = 0x1 << 3
}
Then
alien.physicsBody?.contactTestBitMask = PhysicsCategory.torpedo | PhysicsCategory.shuttle
Hope this helps.
So I've actually managed to solve my problem yesterday. I'm posting the updated code in case it could help someone.
Outside the class:
struct PhysicsCategory {
static let player : UInt32 = 0x1 << 0
static let bullet : UInt32 = 0x1 << 1
static let enemy : UInt32 = 0x1 << 2}
And then, after applying the phyics to each sprite as i wrote before, inside the class:
//Contact with bullet
func contactWithBullet(enemy : SKSpriteNode, bullet: SKSpriteNode) {
enemy.removeFromParent()
bullet.removeFromParent()
score += 1
updateLabels()
}
//contact with player
func contactWithPlayer(player : SKSpriteNode, enemy : SKSpriteNode) {
enemy.removeFromParent()
lives -= 1
updateLabels() //another function that changes the score and lives labels
}
//CONTACT DETECTION
func didBeginContact(contact: SKPhysicsContact) {
let firstBody : SKPhysicsBody = contact.bodyA
let secondBody : SKPhysicsBody = contact.bodyB
if (firstBody.categoryBitMask == PhysicsCategory.enemy && secondBody.categoryBitMask == PhysicsCategory.bullet || firstBody.categoryBitMask == PhysicsCategory.bullet && secondBody.categoryBitMask == PhysicsCategory.enemy) {
contactWithBullet(firstBody.node as! SKSpriteNode, bullet: secondBody.node as! SKSpriteNode)
checkScore()
enemiesInWave -= 1
} else if (firstBody.categoryBitMask == PhysicsCategory.enemy && secondBody.categoryBitMask == PhysicsCategory.player || firstBody.categoryBitMask == PhysicsCategory.player && secondBody.categoryBitMask == PhysicsCategory.enemy) {
contactWithPlayer(firstBody.node as! SKSpriteNode, enemy: secondBody.node as! SKSpriteNode)
checkLives()
enemiesInWave -= 1
}
}