Search code examples
iosswiftsprite-kitskspritenodeskphysicsbody

SKPhysicsBody from Texture gives null on collision in Spritekit


I am creating a game where character jumps and collect points (SOME OBJECT). When you tap character perform some animation from atlas. When it touches the points, score is increased by 1. I am using SKTexture to create physicbody of the character. When my character touches the point it crashes and show me this error

fatal error: unexpectedly found nil while unwrapping an Optional value 

//Code

 Cat = SKSpriteNode(imageNamed: "cat");
  Cat.size = CGSize(width: 100, height: 100)
  Cat.position = CGPoint(x: self.frame.width/2, y: self.frame.height/2)

  //physics 
  Cat.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "cat"), size:CGSizeMake(100, 100))

But if i create Physicsbody from rectangle or circle it works just fine. Maybe its to do with atlases i am using to animate my character.

Cat.physicsBody = SKPhysicsBody(circleOfRadius: self.frame.width/2)

When the character touches the mushroom it crashes. Working Demo: https://github.com/nak1b/demo-game-error


Solution

  • The problem with your code is that didBeginContact is being called multiple times... You can check this by putting a print() statement inside of that if block.

    As you already know, an SKPhysicsBody object has a property called node. It is an optional property. It represents the node that this body is connected to. In your case that is a mushroom node.

    So when didBeginContact is called for the first time, because you remove a mushroom node from its parent, bodyA.node becomes nil. Next time doing bodyA.node!.removeFromParent() will produce a crash, because, as I said, bodyA.node is now nil but you are using forced unwrapping to access underlying value of an optional.

    Use forced unwrapping only when you are 100% positive that underlying value is not nil (or if you intentionally want to trigger an error during the development phase). Otherwise use optional binding or optional chaining. Like this:

    func didBeginContact(contact: SKPhysicsContact) {
    
        var firstBody, secondBody: SKPhysicsBody
    
        //Do this to avoid double checking
        if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
    
            firstBody = contact.bodyA
            secondBody = contact.bodyB
        } else {
    
            firstBody = contact.bodyB
            secondBody = contact.bodyA
        }
    
        //No need for double-checking now
        if ((firstBody.categoryBitMask & PhysicsCategory.Panda) != 0 &&
            (secondBody.categoryBitMask & PhysicsCategory.Score != 0)) {
    
                //This will fail gracefully if node property is nil
                secondBody.node?.removeFromParent()
        }
    }