0
votes

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;
}

}

2
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
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
I think this is a duplicate of stackoverflow.com/q/39505583/1430420Steve Ives
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

2 Answers

0
votes

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.

0
votes

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.