Why SpriteKit bodies don't Contact or Collide

Just a short post today after spending over an hour trying to debug some resolutely difficult to contact bodies in SpriteKit... 

  1. If you want body A to detect contacts with body B make sure that bodyA.contactTestBitMask & bodyB.categoryBitMask != 0 (that is, bodyA is looking for contacts with the category bodyB is in)
  2. If you want body A to collide with body B make sure that bodyA.collisionBitMask & bodyB.categoryBitMask != 0 (that is, bodyA will collide with the category bodyB is in)
  3. Remember, these are not reflective, you can have A contact B and not have B contact A
  4. This is a subtle one... at least one of the two bodies must by dynamic. You can get away with one or the other not being dynamic, but SpriteKit physics assumes that if two "static" bodies weren't touching in the beginning they will never touch again. I tend to try and make sure the main "actors" are just not affectedByGravity if I don't want them dropping to the ground or set global gravity to 0,0

I managed to have a coding error set the categoryBitMask almost correctly (much worse than completely wrong!), and fall foul of two non-dynamic bodies (meaning I chased down lots of blind alleys)... In the end I wrote something simple that will check for any two nodes what kinds of interactions (if any) they can have, here it is...

func debugInteractions(between nodeA: SKNode,`and` nodeB: SKNode){
    guard let bodyA = nodeA.physicsBody else {
        print("nodeA does not have a physics body")
        return
    }
    
    guard let bodyB = nodeB.physicsBody else {
        print("nodeB does not have a physics body")
        return
    }
    
    guard bodyB.dynamic || bodyA.dynamic else {
        print("Neither bodyA or bodyB are dynamic")
        return
    }
    
    if bodyA.contactTestBitMask & bodyB.categoryBitMask == 0 {
        print("bodyA does not include bodyB in its contactBitMask")
    }
    
    if bodyA.collisionBitMask & bodyB.categoryBitMask == 0 {
        print("bodyA does not include bodyB in its collisionBitMask")
    }
    
    if bodyB.contactTestBitMask & bodyA.categoryBitMask == 0 {
        print("bodyB does not include bodyA in its contactBitMask")
    }
    
    if bodyB.collisionBitMask & bodyA.categoryBitMask == 0 {
        print("bodyB does not include bodyA in its collisionBitMask")
    }
}