0

I am working on a SpriteKit game where in one of the scenes I have to update score based on collision between two sprites. I intend to increment score by 1 whenever those two SKSpriteNodes collide with each other. There is a collision detection method in SpriteKit, which takes care of collision detection itself. I am using that default method didBeginContact: to detect collision, remove one of the objects involved in collision and increment score by 1. There are multiple objects of the same kind falling from the top and there is a basket like object that a player can move horizontally to catch those falling objects. On colliding with the basket, objects falling from the top are removed and score get incremented. The issue is very simple, that the score is being incremented not only by 1, but by 2,3,4,5 as well. That means instead of detecting single collision as it should be the case, it is detecting more than one collisions and hence incrementing score accordingly.
I have viewed here another question like this but that solution can never apply to mine. In my game for a limited time, similar objects keep falling from top until the time ends. Is there any possible way to solve this issue. I have tried using bool variable like didCollide inside collision detection method and even in a separate score incrementing method but the issue does not get resolved.
Here is the code I am trying in collision detection method.

-(void)didBeginContact:(SKPhysicsContact *)contact {

if (contact.bodyA.categoryBitMask == Stones) {
    [contact.bodyA.node removeFromParent];

    if (contactOccurred == NO) {
     contactOccurred = YES;
     [self updateScore:contactOccurred];
     }

}
else {
    [contact.bodyB.node removeFromParent];

    if (contactOccurred == NO) {
    ContactOccurred = YES;
    [self updateScore:contactOccurred];
     }        
  }
}  

Code snippet for the method to increment score is here.

-(void)updateScore:(BOOL)collisionOccurred {

if (collisionOccurred == YES) {

    contactOccurred = NO;
    self.score= self.score + 1;
}

}

Alex kiany
  • 105
  • 7
  • You need to explain what you have tried, the results you got and relevant small snippets of code that you believe are the source of your issue(s). In its current form, your question is impossible to answer within the guidelines. – sangony Apr 28 '18 at 23:18
  • I have added code snippets. There must be some way to stop multiple calls to didBeginContact method where it should only be once every collision. – Alex kiany Apr 29 '18 at 01:42
  • I think this is a duplicate of https://stackoverflow.com/q/39505583/1430420 – Steve Ives Apr 30 '18 at 11:55
  • It might be the same question to the one pointed out but that question wasn't successfully answered. You had provided an answer with what finally worked for you but that wasn't really a solution. Issue can be addressed logically by making sure didBeginContact method is not called more than once during one collision. – Alex kiany May 02 '18 at 22:42

2 Answers2

0

When you are using SKSpriteNodes whose SKPhysicsBodies have irregular shapes they can have more than one contact point when they are touching. That's why didBeginContact can be called multiple times for the same two SKSpriteNodes before the Node is removed from its parent.

See this example for a contact with 2 contact points (which causes didBeginContact to be called twice for the same two objects):

enter image description here

To avoid this you can change the physicsBody's categoryBitMask in didBeginContact so that all following contacts are not recognized any more:

-(void)didBeginContact:(SKPhysicsContact *)contact {

    if (contact.bodyA.categoryBitMask == Stones) {
        contact.bodyA.categoryBitMask = None
        [contact.bodyA.node removeFromParent];
        self.score= self.score + 1;
    } else if (contact.bodyB.categoryBitMask == Stones) {
        contact.bodyB.categoryBitMask = None
        [contact.bodyB.node removeFromParent];
        self.score= self.score + 1;
    }       
}

Obviously you have to define the None bitmask value and exclude it from the contactTestBitMask of your basket like SKSpriteNode. That way after changing the categoryBitMask of your Stone SKSpriteNode from Stones to None all further contacts will be ignored.

joern
  • 27,354
  • 7
  • 90
  • 105
  • Are you sure that works? I tried it and found it prevented subsequent collision, but not the multiple contacts that had already been queued and that ‘didBegin‘ is called for. – Steve Ives Apr 29 '18 at 11:43
  • It worked for me, I had the same problem last week and fixed it with this solution. – joern Apr 29 '18 at 11:45
  • Thanks for your answer Joern but it did not work for me. I have created a new category to remove stones but it still does not work. Moreover my stones shaped are perfectly oval so there is no chance of having contact with basket on multiple points. – Alex kiany Apr 29 '18 at 13:14
  • The more fast is the speed of moving basked, more score is incremented on collision with a stone i.e. ranging from 2 to 6 or even 7. Basket is moved horizontally with finger. – Alex kiany Apr 29 '18 at 13:16
  • Hi guys, I just saw that I forgot the `if` clause for `bodyB` after `else`. That's probably the cause it did not work properly for you. (I updated the answer). – joern May 04 '18 at 09:56
0

I have solved the issue myself after some reading from Apple's documentation and experimenting different options. Issue was that the didBeginContact method that is a default method provided by SpriteKit to detect contacts and collisions, was called multiple times as Joern tried to explain in his answer. But objects involved in collision in my game are not of irregular shapes. One kind of objects are oval shaped while the other kind of object is of more or less rectangular shape.Yet the method was being called more than once whenever the contact was made between two objects.

How did I solve it?
I tried applying the technique Joern suggested although I knew it wasn't the real solution I was looking but more of a temporary cover up. But surprisingly it did not work for my game as score would still increment randomly. I removed my oval shaped sprites and replaced them with simple round solid colour sprites just in case my oval shaped sprites were not smooth near the edges. Even then the problem continued that led me to Apple's documentation on this link a link. I came to know that to have best performance from a physical body and better accuracy for collision detection, one should go for simpler physical bodies when possible. A Circular physical body is most performance efficient as it is pretty fast to process and the simplest. Rectangular physics body comes next, followed by a polygonal physics body and physics body from an image texture on last. More complex the phycis body is, more is going to be computational cost and chances of losing accuracy increase. I had created physical bodies of my colliding objects using image texture. Physical bodies created from texture somehow were the cause (or at least in my case) why didContactMethod was being called multiple times. Even physical body of a simple round sprite created from texture was incrementing score by 2 instead of 1. I changed the code to create physical bodies of circular shape for my oval shaped sprites and everything is perfect now without needing to change category for to be removed nodes or any boolean flag.

Avoiding multiple calls to didBeginContact method by use of Boolean flags or any other way can be a cover up but not the solution which works in few cases but won't work in others. Try using simplest physics bodies where possible especially when you start getting inaccurate results from collisions and contacts.

Alex kiany
  • 105
  • 7