Search code examples
swiftsprite-kitcollision-detectionphysicsoption-type

Optional unwrapping SKPhysics error


Before I'm crucified with downvotes let me say I have done research into this and I still cannot understand why I am getting this error.

I have a core that the player is trying to defend and you can shoot little lasers out from it to defend against incoming meteors. Well, I have everything set up and working (most of the time anyways), but every once and while when a laser hits a meteor and my collision handling function tries to remove the shot node and meteor node, it throws this error:

fatal error: unexpectedly found nil while unwrapping an Optional value

Again I have done a lot of digging into this and I cannot seem to figure it out.

Here's my didBegin:

func didBegin(_ contact: SKPhysicsContact) {
    if (contact.bodyA.node?.name == "shot") { // shot A
        collisionBetween(first: (contact.bodyA.node)!, second: (contact.bodyB.node)!)
    } else if (contact.bodyB.node?.name == "shot") { // shot B
        collisionBetween(first: (contact.bodyB.node)!, second: (contact.bodyA.node)!)
    }
    if (contact.bodyA.node?.name == "core") { // core A
        collisionBetween(first: (contact.bodyA.node)!, second: (contact.bodyB.node)!)
    } else if (contact.bodyB.node?.name == "core") { // core B
        collisionBetween(first: (contact.bodyB.node)!, second: (contact.bodyB.node)!)
    }

}

and here's my collisionBetween function:

func collisionBetween(first: SKNode, second: SKNode) {

    if first.name == "shot" && second.name == "sMeteor" {
        first.removeFromParent()    // these two only throw the error
        second.removeFromParent()   // every once and a while
    } else if first.name == "sMeteor" && second.name == "shot" {
        first.removeFromParent()    // these two throw the error also
        second.removeFromParent()   // but only on occasion
    } else if first.name == "mMeteor" && second.name == "shot" {
        second.removeFromParent()
    } else if first.name == "shot" && second.name == "mMeteor" {
        first.removeFromParent()
    }
    if first.name == "core" && second.name == "sMeteor" {
        second.removeFromParent() //always throws the error
    } else if first.name == "sMeteor" && second.name == "core" {
        first.removeFromParent() //always throws the error
    }

}

I've been trying to figure this error out for a while now, and I feel like I have a basic understanding of optionals. These errors are only thrown when I try to remove the nodes from the parent self and I have tried many different approaches to solving this problem but cannot for the life of me figure it out. Any help appreciated, thanks!


Solution

  • I strongly suspect that this is caused by SpriteKit generating multiple contact events for the same contact. (i.e. it's calling didBegin()) 2 or more times with the same bodyA & bodyB)

    The first time it is called, everything is fine; the second time, things have been removed and some nodes and/or bodies are nil.

    Check this answer to see if it helps : Sprite-Kit registering multiple collisions for single contact

    If you'd put some print statements in your didBegin() e.g.

    func didBegin(_ contact: SKPhysicsContact) {
        print("Contact between \(contact.bodyA.node?.name) & \(contact.bodyB.node?.name)")
        if (contact.bodyA.node?.name == "shot") { // shot A
    

    you'd probably have seen in the log:

    Contact between shot and sMeteor
    Contact between shot and sMeteor
    Contact between shot and sMeteor
    

    when only 1 contact has occurred.

    Also, your didBegin() and collisionBetween()functions look overly complicated (plus collisionBetween should really be called contactBetween()).

    I find a neater way to code didBegin() is :

     func didBegin(contact: SKPhysicsContact) {
                let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
    
                switch contactMask {
    
                case categoryBitMask.shot | categoryBitMask.sMeteor:
                   print("Collision between shot and sMeteor")
                   contact.bodyA.removeFromParent()
                   contact.bodyB.removeFromParent()
    
                case categoryBitMask.shot | categoryBitMask.mMeteor:
                   print("Collision between shot and mMeteor")
                   let shot = contact.bodyA.categoryBitMask == categoryBitMask.shot ? contact.bodyA.node! : contact.bodyB.node!
                   shot.removeFromParent()
    
                case categoryBitMask.core | categoryBitMask.sMeteor:
                   print("Collision between core and sMeteor")
                   let meteor = contact.bodyA.categoryBitMask == categoryBitMask.sMeteor ? contact.bodyA.node! : contact.bodyB.node!
                   meteor.removeFromParent()
    
                default :
                   //Some other contact has occurred
                   print("Some other contact")
            }  
        }
    

    This is really only safe if your nodes only belong to one category at a time, which is up to you to determine.

    What happens if 'core' and 'mMeteor' make contact?