0
votes

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");
        } 
2

2 Answers

1
votes

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.

0
votes

I'm not entirely sure what the rules are on SO about multiple answers, but this is an alternative for checking for collisions, one I use all the time:

In your viewDidLoad method:

NSTimer *g = [[NSTimer alloc] initWithFireDate:[NSDate date]
                                      interval:.1
                                        target:self
                                      selector:@selector(checkForCollisions)
                                      userInfo:nil repeats:YES];

NSRunLoop *r = [NSRunLoop currentRunLoop];
[r addTimer:g forMode: NSDefaultRunLoopMode];
[g release];

In your checkForCollisions method:

//without knowing what you are checking collisions for and
//why you are checking for collisions, I can't be too specific here
//but this code will do what you want

for(someObject* objectOfInterest in yourArrayOfObjectsToBeCompared)) {
    CALayer* layerOne = objectOfInterest.layer.presentationLayer;
    CGRect rectOne = layerOne.frame;
    for(someObject* compareObject in yourArrayOfObjectsToBeCompared) {
        CALayer* layerTwo = compareObject.layer.presentationLayer;
        CGRect rectTwo = layerTwo.frame;
        if(CGRectIntersectsRect(rectOne, rectTwo) && ![compareObject isEqual:objectOfInterest]) {
            //collision detected
        }
    }

This code just loops through your array of objects for comparing, then makes a CALayer copy of each object's presentation layer. This step is crucial because taking the regular frame of the object will not give its current position; that will give its destination position. The code then does the same again, comparing it with every other object in the array.