Why are didBeginContact called multiple times?

Solution 1:

The reason why the didBeginContact is being fired multiple times is because you have multiple contact points happening on concave shapes.

If you look at the picture below, you will see I have 2 sprites, a black star and a red rectangle. When the black star hits the red rectangle, it hits it on multiple points, circled in blue. Sprite Kit will then do a call for each line intersection, so that the developer can use the contactPoint variable for each of these contacts.

enter image description here

Solution 2:

I had the same problem (score increasing multiple times for a single enemy destroyed and multiple life points being lost for a single instance of damage.) A user on the Apple forums thinks that it's a bug in [SKPhysicsBody bodyWithTexture:size:] but I don't believe that's the case, because it was happening with other constructors too.

First off, the categoryBitMask and contactTestBitMask are very important, obviously. Take a look at Apple's SpriteKit Physics Collisions sample code:

// Contacts are often a double dispatch problem; the effect you want is based on the type of both bodies in the contact. This sample this in a brute force way, by checking the types of each. A more complicated example might use methods on objects to perform the type checking.

// The contacts can appear in either order, and so normally you'd need to check each against the other. In this example, the category types are well ordered, so the code swaps the two bodies if they are out of order. This allows the code to only test collisions once.

What I did to solve it was setting a flag after handling each condition. In my case, I was testing whether bodyA.node.parent was nil in didBeginContact, because I called removeFromParent() on the missile/enemy nodes to destroy them.

I think you should expect the event to fire multiple times and your code in there has to make sure it's processed only once.

Solution 3:

I figured out easy solution:

Just change either body's categoryBitMask value to 0 or non-used value right after it detected contact.

For example:

if (firstBody.categoryBitMask == padCategory && secondBody.categoryBitMask == colorBallCategory) {

      secondBody.categoryBitMask = 0;

      // DO OTHER THING HERE

}

Solution 4:

I came across the same issue. In my case the didBeginContact() was called many times (I counted up to 5 times) for one contact of a bullet with the enemy. As the bullet is a simple circle format, I agree with @SFX that it cannot be a bug just in Texture-Bodies. The tests have shown that there was no call to update() between the didBeginContact() calls. So the solution is simple (Swift):

var updatesCalled = 0
...
internal update() {
  updatesCalled ++
}
...
internal func didBeginContact(contact: SKPhysicsContact) {
    NSLog("didBeginContact: (\(contact.contactPoint.x), \(contact.contactPoint.y)), \(updatesCalled)")
    if(updatesCalled == 0) {return} // No real change since last call
    updatesCalled = 0
    ... your code here ...
}

I tried didEndContact() but that was not called at all. I didn't investigate further into this.

BTW: I just switched from Android, and I'm impressed by the easiness and stability of this System :-)

Solution 5:

Here is an option that makes the player invulnerable after being hit for a set time:

A. Create a variable that makes the player invulnerable to losing a life after being hit for a few seconds.

  1. Create a global Boolean variable called isInvuln (set to FALSE) and an NSTimeInterval called invulnTime.
  2. In the method that handles the player and enemy making contact, check to see if isInvuln is False before taking a life. (if isInvuln is true ... do nothing)
  3. If isInvuln is false, take a life then set isInvuln to true.

     if(self.isInvuln == FALSE){
          self.player.lives-=1;
          self.isInvuln = True;}
    
  4. Add to your updateWithCurrentTime:

     if(self.isInvuln==True){
     self.invulnTime += timeSinceLast;}
    
     if (self.invulnTime > 3) {             
         self.isInvuln = FALSE:}
         self.invulnTime= 0;
    

This will make it so that when an enemy and player collide, the player loses a life and becomes invulnerable 3 seconds. After that 3 seconds, the player can take damage again. If the enemy contacts the player within the 3 invulnerable seconds, the contact method does nothing. Hope this helps spark ideas to tackle your problem.