Search code examples
ioscollision-detectionbox2dcontacts

Prevent multiple callbacks for one collision with b2ContactListener?


I downloaded Ray Wenderlich's simple contact listener from this tutorial: http://www.raywenderlich.com/606/how-to-use-box2d-for-just-collision-detection-with-cocos2d-iphone

Currently I am using the code below which is pretty much the implementation of the Ray's custom b2ContactListener in my CCLayer. But the problem is, is that there are multiple callbacks for one collision.

Can someone show me how to make it so that the collision will only fire once until the 2 objects untouch and then retouch again?

This is the code I am currently using:

std::vector<b2Body *>toDestroy; 
std::vector<MyContact>::iterator pos;
for(pos = _contactListener->_contacts.begin(); pos != _contactListener->_contacts.end(); ++pos) {
    MyContact contact = *pos;

    // Get the box2d bodies for each object
    b2Body *bodyA = contact.fixtureA->GetBody();
    b2Body *bodyB = contact.fixtureB->GetBody();
    if (bodyA->GetUserData() != NULL && bodyB->GetUserData() != NULL) {
        CCSprite *spriteA = (CCSprite *) bodyA->GetUserData();
        CCSprite *spriteB = (CCSprite *) bodyB->GetUserData();

        if ((spriteA.tag == 1 && spriteB.tag == 2) || (spriteA.tag == 2 && spriteB.tag == 1)) {
             NSLog(@"tag 1 hit tag 2");
        } 

Thanks!

Edit1:

if ((hasDetectedCollision == NO) && ( (spriteA.tag == 1 && spriteB.tag == 2) || (spriteA.tag == 2 && spriteB.tag == 1) ) ) {
                hasDetectedCollision = YES;
                NSLog(@"tag 1 hit tag 2");
                [self doSomethingWhenCollisionHappens];
            }

- (void)doSomethingWhenCollisionHappens {
    //here you might want to remove an object because of a collision
    //or maybe change the color of the objects that collided
    //I can't provide more details of what you might want
    //to do without seeing more of your code
    //but when you are ready to start receiving collision updates again
    //set hasDetectedCollision to NO again
    [yesLabel setString:[NSString stringWithFormat:@"Yes: %d", yesNumber++]];
    hasDetectedCollision = NO;
}

Then in my init method:

hasDetectedCollision = NO;

New Edit:

if (!hasDetectedCollision && ((spriteA.tag == 1 && spriteB.tag == 4) || (spriteA.tag == 4 && spriteB.tag == 1))) {
            hasDetectedCollision = YES;
            [yesLabel setString:[NSString stringWithFormat:@"Yes:%d", yesInt++]];
            NSLog(@"tag 1 hit tag 4");
        } 

Solution

  • I'm not familiar with Box2d, but from what you have described in your problem, this should help you out:

    In your .h:

    @interface foo : FooBar {
        BOOL hasDetectedCollision;
    }
    

    and in your .m:

    - (void)viewDidLoad {
        hasDetectedCollision = NO;
    }
    

    then simply replace

    if ((spriteA.tag == 1 && spriteB.tag == 2) || (spriteA.tag == 2 && spriteB.tag == 1)) {
        NSLog(@"tag 1 hit tag 2");
    } 
    

    with

    if ((hasDetectedCollision == NO) && ( (spriteA.tag == 1 && spriteB.tag == 2) || (spriteA.tag == 2 && spriteB.tag == 1) ) ) {
        hasDetectedCollision = YES;
        NSLog(@"tag 1 hit tag 2");
        [self doSomethingWhenCollisionHappens];
    } 
    

    and then:

    - (void)doSomethingWhenCollisionHappens {
        //here you might want to remove an object because of a collision
        //or maybe change the color of the objects that collided
        //I can't provide more details of what you might want
        //to do without seeing more of your code
        //but when you are ready to start receiving collision updates again
        //set hasDetectedCollision to NO again
        hasDetectedCollision = NO;
    }
    

    Then whenever you are done doing whatever you want to do when you detect a collision, set hasDetectedCollision back to no, and you can start over.

    Edit:

    std::vector<b2Body *>toDestroy; 
    std::vector<MyContact>::iterator pos;
    for(pos = _contactListener->_contacts.begin(); pos != _contactListener->_contacts.end(); ++pos) {
    MyContact contact = *pos;
    
    // Get the box2d bodies for each object
    b2Body *bodyA = contact.fixtureA->GetBody();
    b2Body *bodyB = contact.fixtureB->GetBody();
    if (bodyA->GetUserData() != NULL && bodyB->GetUserData() != NULL) {
        CCSprite *spriteA = (CCSprite *) bodyA->GetUserData();
        CCSprite *spriteB = (CCSprite *) bodyB->GetUserData();
    
        if(hasDetectedCollision == NO)
            NSLog(@"hasDetectedCollision == NO");
    
        if ((spriteA.tag == 1 && spriteB.tag == 2) || (spriteA.tag == 2 && spriteB.tag == 1)) {
             NSLog(@"tag 1 hit tag 2");
        } 
    

    If the if statement checking the state of hasDetectedCollision gets executed, then the problem is not with the boolean but with your collision detection.