0

I'm still a beginner in Swift and I'm having some trouble with collision detections in SpriteKit. I used this question from StackOverFlow which was great in showing my how to construct things neatly. But I'm having problems with my didBegin function, which does not even get called to at all. I'm hoping I missed something simple out that you guys can take a look at for me.

Thanks in advance.

Here is my PhysicsCatagoies struct:

import Foundation
import SpriteKit

struct PhysicsCatagories: OptionSet {

    let rawValue: UInt32
    init(rawValue: UInt32) { self.rawValue = rawValue }

    static let None = PhysicsCatagories(rawValue: 0b00000) // Binary for 0
    static let Player = PhysicsCatagories(rawValue: 0b00001) // Binary for 1
    static let EnemyBullet = PhysicsCatagories(rawValue: 0b00010) // Binary for 2
    static let PlayerBullet = PhysicsCatagories(rawValue: 0b00100) // Binary for 4
    static let Enemy = PhysicsCatagories(rawValue: 0b01000) // Binary for 8
    static let Boss = PhysicsCatagories(rawValue: 0b10000) // Binary for 16
}

extension SKPhysicsBody {
    var category: PhysicsCatagories {
        get {
            return PhysicsCatagories(rawValue: self.categoryBitMask)
        }
        set(newValue) {
            self.categoryBitMask = newValue.rawValue
        }
    }
}

And here is how I assigned my nodes in GameScene:

player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
        player.physicsBody!.affectedByGravity = false
        player.physicsBody!.categoryBitMask = PhysicsCatagories.Player.rawValue
        player.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
        player.physicsBody!.category = [.Enemy, .EnemyBullet, .Boss]

bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.size)
        bullet.physicsBody!.affectedByGravity = false
        bullet.physicsBody!.categoryBitMask = PhysicsCatagories.PlayerBullet.rawValue
        bullet.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
        bullet.physicsBody!.category = [.Enemy, .Boss]

enemy.physicsBody = SKPhysicsBody(rectangleOf: enemy.size)
        enemy.physicsBody!.affectedByGravity = false
        enemy.physicsBody!.categoryBitMask = PhysicsCatagories.Enemy.rawValue
        enemy.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
        enemy.physicsBody!.category = [.Player, .PlayerBullet]

enemyBullet.physicsBody = SKPhysicsBody(rectangleOf: enemyBullet.size)
        enemyBullet.physicsBody!.affectedByGravity = false
        enemyBullet.physicsBody!.categoryBitMask = PhysicsCatagories.EnemyBullet.rawValue
        enemyBullet.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
        enemyBullet.physicsBody!.category = [.Player]

boss.physicsBody = SKPhysicsBody(rectangleOf: boss.size)
        boss.physicsBody!.affectedByGravity = false
        boss.physicsBody!.categoryBitMask = PhysicsCatagories.Boss.rawValue
        boss.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
        boss.physicsBody!.category = [.Player, .PlayerBullet]

bulletSpecial.physicsBody = SKPhysicsBody(rectangleOf: bulletSpecial.size)
            bulletSpecial.physicsBody!.affectedByGravity = false
            bulletSpecial.physicsBody!.categoryBitMask = PhysicsCatagories.PlayerBullet.rawValue
            bulletSpecial.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
            bulletSpecial.physicsBody!.category = [.Enemy, .Boss]

Finally, this is my didBegin function, which does not seem to work at all:

func didBegin(_ contact: SKPhysicsContact) {

        let contactCategory: PhysicsCatagories = [contact.bodyA.category, contact.bodyB.category]

        switch contactCategory {

        case [.Player, .Enemy]:
            print("player has hit enemy")
        case [.PlayerBullet, .Enemy]:
            print("player bullet has hit enemy")
        case [.PlayerBullet, .Boss]:
            print("player bullet has hit boss")
        case [.Player, .Boss]:
            print("player has hit boss")
        case [.Player, .EnemyBullet]:
            print("player has hit enemy bullet")
        default:
            preconditionFailure("Unexpected collision type: \(contactCategory)")
        }
    }
Jack Richards
  • 133
  • 1
  • 7
  • `SKPhysicsBody` doesn’t have a `category` property. Should this be `contactTestBitMask`? – Steve Ives Nov 28 '17 at 19:55
  • @SteveIves Yes it does. I defined it in my extension. – Jack Richards Nov 28 '17 at 20:54
  • There’s no reference to a `category` property of `physicsBody` in that linked question . – Steve Ives Nov 28 '17 at 20:57
  • Ok - but why have you added an extension when you could just use `categoryBitMask`? It confuses people – Steve Ives Nov 28 '17 at 20:59
  • @SteveIves In Step 1. Defining Catagories. Just below the struct. – Jack Richards Nov 28 '17 at 21:00
  • Add a `print(“didBegin called”)` as the first line of `didBegin`. If that gets called then at leSt you’ll know that something is working. Have you made your class a `physicsContactDelegate` and set the `delegate` property? – Steve Ives Nov 28 '17 at 21:01
  • @SteveIves Because it's a convenience accessor like the answer said. And yes I've done both those things and the print statement does not execute. – Jack Richards Nov 28 '17 at 21:02
  • The question you’ve based your code on doesn’t seem to use this weird way of setting/accessing the `categoryBitMask` Are you absolutely sure that your extension, OptionSet technique is working? What’s the advantage of doing it this way? I ask because if `didBegin` isn’t even being called, then SK isn’t registering any contacts at all. – Steve Ives Nov 28 '17 at 21:07
  • @SteveIves Not the question, but the answer to the question is what I've based my code on. He goes through all the steps and explains why and how it works. It seemed to work for that guy, so why isn't it working for me? – Jack Richards Nov 28 '17 at 21:11
  • Jack - OK sorry - missed that. Give me 30 minutes or so and I'll see if I can post an answer using my preferred code style - I've not used categories like that. – Steve Ives Nov 28 '17 at 21:16
  • @SteveIves Ok thanks. – Jack Richards Nov 28 '17 at 21:20

1 Answers1

1

I've not used the OptionSet technique for cagegoryBitMasks, so here's how I'd do it:

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

//Physics categories
let PlayerCategory:         UInt32 = 1 << 0 // b'00001'
let EnemyBulletCategory:    UInt32 = 1 << 1 // b'00010'
let PlayerBulletCategory:   UInt32 = 1 << 2 // b'00100'
let EnemyCategory:          UInt32 = 1 << 3 // b'01000'
let BossCategory:           UInt32 = 1 << 4 // b'10000'

class GameScene: SKScene, SKPhysicsContactDelegate {
   physicsWorld.contactDelegate = self

Assign the categories (usually in didMove(to view:) :

player.physicsBody.catgeoryBitMask = PlayerCategory
bullet.physicsBody.catgeoryBitMask = BulletCategory
enemy.physicsBody.catgeoryBitMask = EnemyCategory
enemyBullet.physicsBody.catgeoryBitMask = EnemyBulletCategory
boss.physicsBody.catgeoryBitMask = BossCategory

(not sure about bulletSpecial - looks the same as bullet)

Set up contact detection:

player.physicsBody?.contactTestBitMask = EnemyCategory | EnemyBulletCategory | BossCategory
bullet.physicsBody?.contactTestBitMask = EnemyCategory | BossCategory
enemy.physicsBody?.contactTestBitMask = PlayerCategory | PlayerBulletCategory
enemyBullet.physicsBody?.contactTestBitMask = PlayerCategory
boss.physicsBody?.contactTestBitMask = PlayerCategory | PlayerBulletCategory

Turn off collisions: (on by default)

player.physicsBody?.collisionBitMask = 0
bullet.physicsBody?.collisionBitMask = 0
enemy.physicsBody?.collisionBitMask = 0
enemyBullet.physicsBody?.collisionBitMask = 0
boss.physicsBody?.collisionBitMask = 0

Implement didBegin:

  func didBegin(_ contact: SKPhysicsContact) {

    print("didBeginContact 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 PlayerCategory | EnemyCategory:
        print("player has hit enemy")
     case PlayerBulletCategory | EnemyCategory:
        print("player bullet has hit enemy")
     case PlayerBulletCategory | BossCategory:
        print("player bullet has hit boss")
     case PlayerCategory | BossCategory:
        print("player has hit boss")
     case PlayerCategory | EnemyBulletCategory:
        print("player has hit enemy bullet")
    default:
        print("Undetected collision occurred")
    }
  }

It's a bit late here, so hopefully I haven't made any stupid mistakes.

=======================

You could also include this function and then call it via checkPhysics() once you have set up all your physics bodies and collisions and contact bit masks. It will go through every node and print out what collides with what and what contacts what (it doesn't check the isDynamic property, so watch out for that):

//MARK: - Analyse the collision/contact set up.
func checkPhysics() {

    // Create an array of all the nodes with physicsBodies
    var physicsNodes = [SKNode]()

    //Get all physics bodies
    enumerateChildNodes(withName: "//.") { node, _ in
        if let _ = node.physicsBody {
            physicsNodes.append(node)
        } else {
            print("\(String(describing: node.name)) does not have a physics body so cannot collide or be involved in contacts.")
        }
    }

    //For each node, check it's category against every other node's collion and contctTest bit mask
    for node in physicsNodes {
        let category = node.physicsBody!.categoryBitMask
        // Identify the node by its category if the name is blank
        let name = node.name != nil ? node.name : "Category \(category)"

        if category == UInt32.max {print("Category for \(String(describing: name)) does not appear to be set correctly as \(category)")}

        let collisionMask = node.physicsBody!.collisionBitMask
        let contactMask = node.physicsBody!.contactTestBitMask

        // If all bits of the collisonmask set, just say it collides with everything.
        if collisionMask == UInt32.max {
            print("\(name) collides with everything")
        }

        for otherNode in physicsNodes {
            if (node != otherNode) && (node.physicsBody?.isDynamic == true) {
                let otherCategory = otherNode.physicsBody!.categoryBitMask
                // Identify the node by its category if the name is blank
                let otherName = otherNode.name != nil ? otherNode.name : "Category \(otherCategory)"

                // If the collisonmask and category match, they will collide
                if ((collisionMask & otherCategory) != 0) && (collisionMask != UInt32.max) {
                    print("\(name) collides with \(String(describing: otherName))")
                }
                // If the contactMAsk and category match, they will contact
                if (contactMask & otherCategory) != 0 {print("\(name) notifies when contacting \(String(describing: otherName))")}
            }
        }
    }
}

It will produce output like:

Optional("shape_blueSquare") collides with Optional("Screen_edge")
Optional("shape_redCircle") collides with Optional("Screen_edge")
Optional("shape_redCircle") collides with Optional("shape_blueSquare")
Optional("shape_redCircle") notifies when contacting Optional("shape_purpleSquare")
Optional("shape_redCircle") collides with Optional("shape_greenRect")
Optional("shape_redCircle") notifies when contacting Optional("shape_greenRect")
Optional("shape_purpleSquare") collides with Optional("Screen_edge")
Optional("shape_purpleSquare") collides with Optional("shape_greenRect")
Category for Optional("shape_greenRect") does not appear to be set correctly as 4294967295
Optional("shape_greenRect") collides with Optional("Screen_edge")
Optional("shape_yellowTriangle") notifies when contacting Optional("shape_redCircle")
Optional("shape_yellowTriangle") collides with Optional("shape_greenRect")
Optional("shape_yellowTriangle") notifies when contacting Optional("shape_greenRect")

etc.
Steve Ives
  • 7,894
  • 3
  • 24
  • 55
  • Thanks for the answer. I'll check it tomorrow and let you know if it works out! – Jack Richards Nov 28 '17 at 22:08
  • No problem Jack - I had loads of stuff like this on SO Documentation - I need to find somewhere to repost it all. – Steve Ives Nov 28 '17 at 22:10
  • Hi Steve. Just wanted to say this code works like a charm, although I'm still not sure why mine didn't work ... must have been something to do with the extension in my struct. Anyhow, thanks for the answer and taking time to help me out! – Jack Richards Nov 29 '17 at 12:56
  • @JackRichards You’re welcome - glad it worked. Did you try the 'checkPhysics()' function? You could use that in your original answer and see what it thinks collides etc and try to debug the extension and the 'optionSet' technique. – Steve Ives Nov 29 '17 at 13:18
  • Haven't began implementing that function yet. I'll let you know when I do though. – Jack Richards Nov 29 '17 at 13:25