Search code examples
iosswiftsprite-kitskphysicsbody

How to Detect collision in Swift, Sprite kit


I was programming this simple space game in Swift until I encountered the problem of detecting collisions. After looking around forums, tutorials, etc, I tried to implement collisions by declaring bitmasks like so:

object 1

    enemy?.physicsBody = SKPhysicsBody(circleOfRadius: ((enemy?.size.width)!/2))
    enemy?.physicsBody?.categoryBitMask = enemyBitMask
    enemy?.physicsBody?.contactTestBitMask = bulletBitMask
    enemy?.physicsBody?.collisionBitMask = 0

object 2

    bullet?.physicsBody? = SKPhysicsBody(rectangleOf: (bullet?.size)!)
    bullet?.physicsBody?.categoryBitMask =  bulletBitMask
    bullet?.physicsBody?.contactTestBitMask =  enemyBitMask
    bullet?.physicsBody?.collisionBitMask = 0
    bullet?.physicsBody?.usesPreciseCollisionDetection = true

I also put a print statement in the

func didBegin(_ contact: SKPhysicsContact) { print("Hello") }

here is how I configure my sprites:

    func CreateNewEnemy() {
    var enemy : SKSpriteNode?

       let moveEnemyDown = SKAction.repeatForever(SKAction.moveBy(x: 0, y: -1, duration: 0.01))
       let rotateEnemy = SKAction.repeatForever(SKAction.rotate(byAngle: 25, duration: 5))

      let enemyXpos = randomNum(high:  self.frame.size.width/2, low: -1 * self.frame.size.width/2)
      let enemyYpos = randomNum(high:  2.5*self.frame.size.height, low: self.frame.size.height/2)
      let enemyOrigin : CGPoint = CGPoint(x: enemyXpos, y: enemyYpos)

      enemy = SKSpriteNode(imageNamed: possibleEnemyImage[Int(arc4random_uniform(4))])
      print(enemy?.size.height)
      enemy?.scale(to: CGSize(width: player.size.height, height: player.size.height))
      print(enemy?.size.height)
      enemy?.position = enemyOrigin
      enemy?.run(moveEnemyDown)
      enemy?.run(rotateEnemy)
      let enemyRadius : CGFloat = (enemy?.size.width)!/2
      print(enemyRadius)

      enemy?.physicsBody? = SKPhysicsBody(circleOfRadius: enemyRadius)

      enemy?.physicsBody?.categoryBitMask = enemyCategory
      enemy?.physicsBody?.contactTestBitMask = bulletCategory
      enemy?.physicsBody?.collisionBitMask = 0
      enemy?.zPosition = 1

      enemiesArray.append(enemy!)
      self.addChild(enemy!)
}

to create enemy (Called in did move to view function)

    func CreateAllEnemies(amountOfEnemies : UInt8) {
    for _ in 0...amountOfEnemies {
        CreateNewEnemy()
    }
}

and the other sprite

    func CreateNewBullet() {
     let bulletOrigin : CGPoint = CGPoint(x: player.position.x, y: player.position.y+player.size.height/2)
     let moveBulletUp = SKAction.repeatForever(SKAction.moveBy(x: 0, y: 3, duration: 0.01))

     var bullet : SKSpriteNode?
     bullet = SKSpriteNode(imageNamed: "bulletImage")
     bullet?.position = bulletOrigin
     bullet?.run(moveBulletUp)

     bullet?.physicsBody? = SKPhysicsBody(rectangleOf: (bullet?.size)!)
     bullet?.physicsBody?.categoryBitMask =  bulletCategory
     bullet?.physicsBody?.contactTestBitMask = enemyCategory
     bullet?.physicsBody?.collisionBitMask = 0
     bullet?.physicsBody?.isDynamic = true
     bullet?.physicsBody?.usesPreciseCollisionDetection = true
     bullet?.zPosition = 1

     bulletsArray.append(bullet!)
     self.addChild(bullet!)
}

this one is created with a timer

bulletTimer = Timer.scheduledTimer(timeInterval: 0.25, target: self, selector: #selector(CreateNewBullet) , userInfo: nil, repeats: true)

Unfortunately it does not print anything in the console after I see the two objects touching.


Solution

    1. Define unique categories, ensure your class is a SKPhysicsContactDelegate and make yourself the physics contact delegate:

    //Physics categories

    let enemyCategory:    UInt32 = 1 << 1
    let bulletCategory:   UInt32 = 1 << 2
    
    class GameScene: SKScene, SKPhysicsContactDelegate {
       self.physicsWorld.contactDelegate = self
    
    1. Assign the categories (usually in didMove(to view:)) :

    enemy.physicsBody.categoryBitMask = enemyCategory

    bullet.physicsBody.categoryBitMask = bulletCategory

    (Make sure you've created physics bodies for each node)

    1. Set up collisions:

    enemy.physicsBody?.collisionBitMask = 0 // enemy collides with nothing bullet.physicsBody?.collisionBitMask = 0 // bullet collides with nothing

    or even:

    for node in [enemy, bullet] {
       node.physicsBody?.collisionBitMask = 0 //  collides with nothing
       }
    
    1. Set up contacts

      bullet.physicsBody?.collisionBitMask = enemyCategory // bullet contacts enemy

    Make sure that at least one of the objects involved in each potential contact has the isDynamic property on its physics body set to true, or no contact will be generated. It is not necessary for both of the objects to be dynamic.

    You should now get didBegin called when the bullet and the enemy make contact. You could code didBegin like this:

      func didBegin(_ contact: SKPhysicsContact) {
    
         print("didBegin entered for \(String(describing: contact.bodyA.node.name)) and \(String(describing: contact.bodyB.node.name))")
    
         let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
    
         switch contactMask {
         case bulletCategory | enemyCategory:
    
            print("bullet and enemy have contacted.")
    
            let bulletNode = contact.bodyA.categoryBitMask == bulletCategory ? contact.bodyA.node : contact.bodyB.node
            enemyHealth -= 10
            bulletNode.removeFromParent
         default:
            print("Some other contact occurred")
         }
    

    }