19

In an iOS game that uses Sprite Kit along with the contact detection in Sprite Kit's build-in physics engine, I decrease the Hero's number lives by one each time he gets in contact with an enemy. This is done from the didBeginContact method. However, it seems like that method is not just called once, when the contact begins, but called continuously as long as the Hero and the enemy overlaps: when I set a breakpoint in that method, I can see, that it is the exact same physics body instances that exist as contact.bodyA and contact.bodyB. The result is, that the Hero will lose multiple lives, even though he only passes one single enemy.

If the Hero meets the same enemy again later, he should get one more live subtracted, and therefore I cannot just maintain a seenEnemies hash set to deal with the problem above.

The question is now: how would you make sure that only one live is subtracted for each Hero/enemy contact?

someName
  • 1,275
  • 2
  • 14
  • 33
  • I would assume there is a `didEndContact` as well. – dandan78 Jun 15 '14 at 09:39
  • Questions seeking debugging help ("why isn't this code working?") must include the desired behavior, a specific problem or error and the shortest code necessary to reproduce it in the question itself. Questions without a clear problem statement are not useful to other readers. – CodeSmile Jun 15 '14 at 13:17
  • The reference documentation claims that the method is only called when contact begins. I read that as "one single time per contact". Apparently, that is not the case. Is the reference documentation really wrong at this point? – someName Jun 15 '14 at 19:35
  • I hade the same problem, and only after 10 min debugging, understand that `didBeginContact`is called two time, at least in my case. – WebOrCode Aug 11 '14 at 18:48

6 Answers6

21

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

Knight0fDragon
  • 16,609
  • 2
  • 23
  • 44
  • 1
    Good point, this might even happen on simple shapes. For example when a circle intersects a line, there are two contact points in most cases. – salocinx Jul 21 '16 at 15:06
  • I din‘t. Still like your illustration very much :) Although I think there are a least 4 contact points in your illustration rather than only 2 ;-p – salocinx Sep 28 '17 at 19:45
  • Yeah haha I noticed that a while back but I never bothered to change it, was just demonstrating an idea. Also, I didn't think it was you, somebody just randomly came in and down-voted without a reason why. Now I have no way of improving the answer. – Knight0fDragon Sep 28 '17 at 19:47
  • yes your answer is still upvoted in my account. you can‘t edit your answer when somebody has downvoted it? – salocinx Sep 28 '17 at 20:13
  • You can, but I don’t know why they down-voted it, so I can’t improve it lol – Knight0fDragon Sep 28 '17 at 20:30
  • perhaps the downvoter was just frustrated and then came along your lovely picture ;D – salocinx Sep 28 '17 at 21:22
18

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.

STO
  • 623
  • 5
  • 9
  • 1
    Using the `SKPhysicsBody(rectangleOfSize: )` constructor "solved" my problem, thank you! Hopefully they fix this bug – YourMJK Nov 14 '15 at 22:02
10

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

}
GeneCode
  • 7,545
  • 8
  • 50
  • 85
6

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 :-)

jboi
  • 11,324
  • 4
  • 36
  • 43
1

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.

meisenman
  • 1,818
  • 1
  • 15
  • 25
  • 2
    I ended up making the hero immune for a period at first contact, thereby circumventing the problem. However, the original question remains unaswered: is didBeginContact purposly getting fired continuously as long as there is a contact, and if that is the case, what would be a good strategy to only register the first the fist contact (and re-enbling contact actions again, once the hero steps out of the contact zone)? – someName Jun 20 '14 at 09:40
  • Try to re-enable contact using didEndContact (instead of using a time) which would do exactly what you are looking for. I cannot speak for didEndContact as I have not really used it. – meisenman Jun 20 '14 at 14:44
  • You could also make the object that takes a life on contact a subclass of SKSpriteNode with a boolean that you can set to false after its first contact. When contact happens, if the boolean is true you take a life and set the boolean to false. If the boolean is false... do nothing – meisenman Jun 20 '14 at 20:42
0

In my experience, didEndContact & didBeginContact are both called multiple times while the objects overlap. This is also happening in SceneKit using iOS 9, so I have to assume it's an intended behavior.

cwgso
  • 401
  • 3
  • 9
  • This is happening for me as well, and it's kinda frustrating. In my case, I'm just doing a contact test with no collision. I want to simply detect if the player is on top of something, inside of it. It annoys me that didEnd is being spam triggered while my player is 100% inside of the contactTest object, having never left its borders. Contact certainly has not ended for any pixel of my character, yet it keeps calling. – DiggyJohn Feb 06 '19 at 02:52