Search code examples
iosswiftsprite-kitcollision-detectiongame-physics

why didBegin has no data about contact bodies?


I am making a game that a player ( ball ) and enemy ( ball ) hit each other. sometimes didBegin func hold information about the two collided bodies and sometimes no, which lead to a crash and throwing an error of exc_bad_instruction , then I check the player and enemy with breakpoints and I find them nil. how that happen that sometimes it work and sometimes don't. please help.

My Code :

private var player: Player!

private func drawPlayer(color: SKColor, radius: CGFloat, cn: String) {
        player = Player.factory.createPlayer(color: color, radius: radius)
        player.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
        player.name = cn
        world.addChild(player)
    }

if (contact.bodyA.categoryBitMask == bodyType.enemy.rawValue) && (contact.bodyB.categoryBitMask == bodyType.player.rawValue) {
        print(contact.bodyA.node?.name)
        print(contact.bodyB.node?.name)
        let player = contact.bodyB.node as? Player
        let hitter = contact.bodyA.node as? Enemy
}

I used the print to get the names sometimes it give me both names and sometimes it gives me both nil . the rest of code I didn't put it as it is not needed here.

THE SOLUTION ::

The problem as steve mentioned in his answer is that sometimes the didBegin method is being called on same collision objects twice . so it crash because at its first call I removed the objects so it finds nil second time. so as steve mentioned you should surround the logic with if and check if the two bodies are nil then this is the second call of didBegin and the objects have already been removed so you should not run the logic or it will run on nothing then crash. and if they are not nil you can run the code and it works.


Solution

  • What happens if bodyA is the player and bodyB is the enemy? I suspect this situation is the cause of your "and sometimes no, which lead to a crash" problem.

    In didBegin() do you remove any nodes from the scene (with removeFromParent)? That could cause the behaviour as SK sometimes generates multiple calls to didBegin for a single collision. If you remove either or both of the nodes involved in the collision the first time that didBegin is called, then the second time it's called (this is within the same game loop) some of your nodes and/or their attributes could be nil.

    Bear in mind that the objects passed to didBegin in the SKMPhysicsContact object are physics bodies and not the nodes themselves. It's possible for the physics bodies to still exist even though you've removed the nodes themselves (in a previous didBegin). This means that unless you access the actual nodes in didBegin, you might not get a crash and if you are doing some simple checks e.g. if the player has hit a coin or an enemy, then you might increase a score twice, or remove two lots of health etc.

    There are many ways to handle this - including, but not limited to:

    • Check if a body is nil before doing anything with it. (If it is nil, then assume you've already processed it's collision and removed it).
    • Add the node to be removed to a set, and then removing all the nodes in the set in didFinishUpdate, after all the calls to didBegin have been made. You might need to set a flag in the nodes userData property to stop it being processed twice.

    Plus some other techniques. Search for sprite-kit multiple collisions.

    To deal with the 'which is bodyA and which is bodyB' problem, I like to code didBegin: like this:

        func didBeginContact(contact: SKPhysicsContact) {
            let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
    
            switch contactMask {
    
            case categoryBitMask.player | categoryBitMask.enemy:
               print("Collision between player and enemy")
               let enemy = contact.bodyA.categoryBitMask == categoryBitMask.thisMine ? contact.bodyA.node! : contact.bodyB.node!
               enemy.explode()
    
            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.