Search code examples
swiftsprite-kitxcode6collision-detectionuint32

Easy SpriteKit Contact Detection (Space Shooter Game) Not Wotking Properly


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!


Solution

  • 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