1
votes

It sounds simple enough in the title, however for the last 3 days i've struggled to do what sounds like a simple thing.

The "MyContactListener.h/mm" method, as advised on the Ray Wenderlich tutorial followed here, worked for a single object, however my game is going to produce several items at regular intervals rather than fixed in place at initiation as per the tutorial.

I ended up having an EXC_BAD_ACCESS when i tried to put the objectBodyFixture in an NSMutibleArray that held all the b2fixture objects created. My intention was to parse through them all on each tick: to look for contacts, and handle appropriately.

Now I'm at a loss for what to do. So just to clarify, I simply need to create falling objects from the sky that destroy when they hit my ground object.

I'm going to provide as much as I can here just in case its not as simple to do as it sounds... As you'll se I'm using the "MyContactListener.h/mm" as advised on the Ray Wenderlich tutorial followed here.


The idea is to drop the created dynamic bodies from the sky at regular intervals, and then destroy them if they fall to hit the ground.

I have a ground object that I created as a static body during the init of the scene:

@implementation HelloWorldLayer
{
    CGSize size;
    b2Body *_groundBodyBottom;
    b2Fixture *_groundBottomFix;
    NSMutableArray *_objectFixtures;
}
-(id) init { if( (self=[super initWithColor:ccc4(50, 180, 220, 255)]) )
{
    // First instance setups
    size = [[CCDirector sharedDirector] winSize];
    _objectFixtures = [[NSMutableArray alloc] init];

    // Create The World
    b2Vec2 gravity = b2Vec2(0.0f, -8.0f); // -128.0
    _world = new b2World(gravity);

     // Create The Ground
     // Bottom
     b2BodyDef groundBottomBodyDef;
     groundBottomBodyDef.type = b2_staticBody;
     groundBottomBodyDef.position.Set((size.width/2)/PTM_RATIO, 60/PTM_RATIO);
     _groundBodyBottom = _world->CreateBody(&groundBottomBodyDef);

     b2PolygonShape groundBottomShape;
     groundBottomShape.SetAsBox((size.width/2)/PTM_RATIO, 1.3);

     b2FixtureDef groundBottomShapeDef;
     groundBottomShapeDef.shape = &groundBottomShape;
     groundBottomShapeDef.density = 10.0f;
     groundBottomShapeDef.friction = 1.0f;
     groundBottomShapeDef.restitution = 0.0f;
     _groundBottomFix = _groundBodyBottom->CreateFixture(&groundBottomShapeDef);

     // Hand off repeating schedules
     [self scheduleUpdate];
     [self schedule:@selector(addAnObjectUpdate:) interval:1];

     // Create contact listener
     _contactListener = new MyContactListener();
     _world->SetContactListener(_contactListener);
}

As you can see the addAnObjectUpdate will fire every second...

-(void)addAnObjectUpdate:(ccTime)dt
{
    CCSprite *objectSprite = [CCSprite spriteWithFile:@"myObject.png"];
    objectSprite.position = ccp(size.width/2,size.height *2);
    objectSprite.scale = 0.10;
    objectSprite.tag = 1;
    [self objectSprite z:30]; //z:30 overlaps clouds in scene

    b2BodyDef objectBodyDef;
    objectBodyDef.type = b2_dynamicBody;
    objectBodyDef.position.set((size.width/2)/PTM_RATIO, (size.height + objectSprite.boundingBox.size.height)/PTM_RATIO);
    objectBodyDef.userData = (__bridge void*)objectSprite;
    _objectBody = _world->CreateBody(& objectBodyDef);

    int scalledPTMRatio = PTM_RATIO * 10; // Needed if the sprite image is scalled...
    int num = 8;
    b2Vec2 verts[] = {
        b2Vec2(-59.8f / scalledPTMRatio, -173.8f / scalledPTMRatio),
        b2Vec2(-5.9f / scalledPTMRatio, -199.5f / scalledPTMRatio),
        b2Vec2(53.8f / scalledPTMRatio, -174.9f / scalledPTMRatio),
        b2Vec2(63.1f / scalledPTMRatio, 198.3f / scalledPTMRatio),
        b2Vec2(-6.7f / scalledPTMRatio, 196.0f / scalledPTMRatio),
        b2Vec2(-53.8f / scalledPTMRatio, 144.7f / scalledPTMRatio),
        b2Vec2(-71.4f / scalledPTMRatio, -104.7f / scalledPTMRatio),
        b2Vec2(-66.4f / scalledPTMRatio, -160.6f / scalledPTMRatio)
    };
    b2PolygonShape objectShape;
    objectShape(verts, num);

    b2FixtureDef objectShapeDef;
    objectShapeDef.shape = & objectShapeDef;
    objectShapeDef.density = 1.0f;
    objectShapeDef.friction = 0.f;
    objectShapeDef.restitution = 0.8f;
    b2Fixture * objectShapeDef = _objectBody->CreateFixture(&objectShapeDef);
    [_objectFixtures addObject:(__bridge NSObject*)objectBodyFixture];  // Heres hoping to access them during the tick:
}

With all this in place, my tick: method is as follows:

-(void)tick:(ccTime) dt
{
    _world->Step(dt, 10, 10);
    // Check For Collissions
     NSLog(@"\n\n\nTick....");
     std::vector<MyContact>::iterator pos;
    for(pos = _contactListener->_contacts.begin(); pos != _contactListener->_contacts.end(); ++pos)
    {
        MyContact contact = *pos;

         for (NSObject *myObject in _objectFixtures)
        {
             if ((contact.fixtureA == _groundBottomFix && contact.fixtureB == (__bridge class b2Fixture *)(myObject)) ||
                (contact.fixtureA == (__bridge class b2Fixture *)(myObject) && contact.fixtureB == _groundBottomFix))
            {
                NSLog(@"myObject touched the ground!");
                [_hud changeLivesTo:[_hud livesRemaining] - 1];
            }
        } // For loop - Array
    } // For loop - contacts/collisions
}

Well I think this is all the relevant code for helping me solve this one, if theres anything more I can provide, to help improve the question, please let me know and I will update the question.

I'de really appreciate your help on this one, thanks!

2

2 Answers

0
votes

This is how I would typically do it.

Use a set to store a list of bodies to destroy:

std::set<b2Body*> bodiesToDestroy;

Make a class that extends b2ContactListener and implements the BeginContact function, and set an instance of this as the contact listener for your world:

class MyContactListener : public b2ContactListener {

    virtual void BeginContact(b2Contact* contact) {
        if ( ... contact is between ground and falling body ... ) {
            bodiesToDestroy.insert( ... the falling body ... );
            ... do other necessary things, eg. change score etc ...
        }
    }

};

After stepping the world, destroy bodies in the list and then clear it:

world->Step( ... ); // contact listener is called inside here

for ( std::set<b2Body*>::iterator it = bodiesToDestroy.begin(); it != bodiesToDestroy.end(); ++it)
    world->DestroyBody( *it );
bodiesToDestroy.clear();

EDIT: You can find more information about where these code snippets fit into the overall picture, at these pages:

http://www.iforce2d.net/b2dtut/collision-callbacks

http://www.iforce2d.net/b2dtut/removing-bodies

Source code for the examples can be found here:

http://www.iforce2d.net/b2dtut/sourcecode

0
votes

I ended up simply adding a tag to the b2FixtureDef.userdata when creating the fixture. I used a struct to make my own object that I could put into the b2FixtureDef.userdata that would contain my tag, and anything else i needed. The struct looks like this:

struct MyUserData { 
    int myTag; 
};

Then when declaring the `b2FixtureDef i add this...

MyUserData *theGroundUserData = (MyUserData*)malloc(sizeof(MyUserData));  // USER DATA BIT
theGroundUserData->myTag = 3;
groundBottomShapeDef.userData = theGroundUserData;

...Saving my tag into a MyUserData object that i later check for in my tick:

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

    if (contact.fixtureA->GetUserData() != NULL && contact.fixtureB->GetUserData() != NULL) {

        int fixtureNameA = ((MyUserData *)contact.fixtureA->GetUserData())->myTag;
        int fixtureNameB = ((MyUserData *)contact.fixtureB->GetUserData())->myTag;
        // Check for an apple colliding with the ground
        if ((fixtureNameA == 2 && fixtureNameB == 3) || (fixtureNameA == 3 && fixtureNameB == 2))
        {
            NSLog(@"Apple touched the ground");
            b2Body *bodyA = contact.fixtureA->GetBody();
            b2Body *bodyB = contact.fixtureB->GetBody();
            if (fixtureNameA == 2)
            {
                if (std::find(toDestroy.begin(), toDestroy.end(), bodyA) == toDestroy.end()) {
                    toDestroy.push_back(bodyA);
                }
            }
            else
            {
                if (std::find(toDestroy.begin(), toDestroy.end(), bodyB) == toDestroy.end()) {
                    toDestroy.push_back(bodyB);
                }
            }
        }
    }
}