How To Make a Catapult Shooting Game with Cocos2D and Box2D Part 2

This is a blog post by iOS Tutorial Team member Gustavo Ambrozio, a software engineer with over 20 years experience, including over three years of iOS experience. He is the founder of CodeCrop Software. You can also find him on Google+. This is the second part of a two part tutorial series where we’ll build […] By Ray Wenderlich.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Contents

Hide contents

Rapid Fire

Before we move on to do some collision detection let’s add some code to attach another bullet after we throw one just so we can make some more destruction.

Add a new method called resetBullet above resetGame to do this:

- (void)resetBullet
{
    if ([enemies count] == 0)
    {
        // game over
        // We'll do something here later
    }
    else if ([self attachBullet])
    {
        [self runAction:[CCMoveTo actionWithDuration:2.0f position:CGPointZero]];
    }
    else
    {
        // We can reset the whole scene here
        // Also, let's do this later
    }
}

On this method we first check to see if we destroyed all the enemies. This won’t happen now as we’re still not destroying the enemies but let’s prepare for this already.

If there still are enemies, we try to attach another bullet. Remember that the attachBullets method returns YES if there are more bullets to attach and NO otherwise. So, if there are more bullets I run a cocos2d action that resets the position of the view to the left half of the scene so I can see my catapult again.

If there are no more bullets we’ll have to reset the whole scene, but this will come later on. Let’s have some fun first.

We now have to call this method at an appropriate time. But what is this appropriate time. Maybe when the bullet finally stops moving after we released it? Maybe a few seconds after we hit the first target? Well, that’s all open for discussion. To keep things simple for now, let’s call this method a few seconds after we release the bullet.

As you remember we do this on the tick method when we destroy the weld joint. So go to that method and find the DestroyJoint call we added and add this line right after:

[self performSelector:@selector(resetBullet) withObject:nil afterDelay:5.0f];

This will wait 5 seconds and then call resetBullet. Now run the project and watch the mayhem!

One last thing for this section. The mayhem is not very natural in my opinion because of one little detail: the objects that collide with the right border of the scene all stay there as if leaning against a wall but there’s no wall there in our world. The objects should fall to the right of the scene but they don’t.

This happens because when we build our world (well, actually this came with the initial cocos2d project) we added fixtures to the 4 corners of the scene. We now can see that the right corner probably should not exist.

So go to the init method and remove this lines:

// remove these lines under the comment "right"
 groundBox.SetAsEdge(b2Vec2(screenSize.width*2.0f/PTM_RATIO,screenSize.height/PTM_RATIO), b2Vec2(screenSize.width*2.0f/PTM_RATIO,0));
 groundBody->CreateFixture(&groundBox,0);

Now run the project again. It should be a little more natural.

More destruction in our world!

Well, as natural as a world of war hungry squirrels, anyway.

The repository tag for this point in the tutorial is TargetsCreation.

It’s Raining Cats and Dogs

We’re almost there! All we need now is a way to detect that the enemies should be destroyed.

We can do this using collision detection but there’s a little problem with a simple collision detection. Our enemies are already colliding with the blocks so simply detecting if the enemies are colliding with something is not enough because the enemies would be destroyed right away.

We could say that for the enemies to be destroyed they have to collide with a bullet. This would be very easy to do but then some enemies would be very hard to destroy. Take the dogs between the two blocks on our scene. It’s very hard to hit it with a bullet but it’s not hard if we throw one of the blocks at it. But we already established that a simple collision with the blocks is not going to work.

What we can do is try to determine the strength of the collision and then determine that we have to destroy an enemy based on a minimum strength.

To do this using Box2d we have to create a contact listener. Ray already explained how to create one during the second part of the breakout game tutorial. So if you haven’t read the tutorial or don’t remember it go ahead and read at least the beginning of this second part. I’ll wait….

There will be some differences though. First we will use a std::set instead of a std::vector. The difference is that the set does not allow duplicate entries so if we have multiple impacts on a target we don’t have to worry about adding them twice to our list.

Another difference is that we’ll have to use the postSolve method as this is where we’ll be able to retrieve the impulse of a contact and thus determine if we have to destroy an enemy.

Control-click on your main classes folder and choose New File. Click iOS\C and C++ on the left, choose “C++ file”, and click Next. Name your file MyContactListener.cpp, and click Save. If it created a .h for you too you’re set, otherwise repeat the above but select “Header File” to create a header file called MyContactLister.h as well.

Open the newly created MyContactLister.h and add this code:

#import "Box2D.h"
#import <set>
#import <algorithm>
 
class MyContactListener : public b2ContactListener {
   
public:
    std::set<b2Body*>contacts;
   
    MyContactListener();
    ~MyContactListener();
   
    virtual void BeginContact(b2Contact* contact);
    virtual void EndContact(b2Contact* contact);
    virtual void PreSolve(b2Contact* contact, const b2Manifold* oldManifold);   
    virtual void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse);
   
};

This is almost the same thing as Ray did except for a set instead of a vector.

Next go to MyContactListener.cpp file and add this:

#include "MyContactListener.h"

MyContactListener::MyContactListener() : contacts()
{
}

MyContactListener::~MyContactListener()
{
}

void MyContactListener::BeginContact(b2Contact* contact)
{
}

void MyContactListener::EndContact(b2Contact* contact)
{
}

void MyContactListener::PreSolve(b2Contact* contact,
                                 const b2Manifold* oldManifold)
{
}

void MyContactListener::PostSolve(b2Contact* contact,
                                  const b2ContactImpulse* impulse)
{
    bool isAEnemy = contact->GetFixtureA()->GetUserData() != NULL;
    bool isBEnemy = contact->GetFixtureB()->GetUserData() != NULL;
    if (isAEnemy || isBEnemy)
    {
        // Should the body break?
        int32 count = contact->GetManifold()->pointCount;
        
        float32 maxImpulse = 0.0f;
        for (int32 i = 0; i < count; ++i)
        {
            maxImpulse = b2Max(maxImpulse, impulse->normalImpulses[i]);
        }
        
        if (maxImpulse > 1.0f)
        {
            // Flag the enemy(ies) for breaking.
            if (isAEnemy)
                contacts.insert(contact->GetFixtureA()->GetBody());
            if (isBEnemy)
                contacts.insert(contact->GetFixtureB()->GetBody());
        }
    }
}

As I mentioned we only implement the PostSolve method.

First we determine if the contact we’re processing involves at least one enemy. Remember on the createTarget method that we “tagged” the enemies using the fixture’s userData? That’s why we did it. See, I told you you’d understand soon.

Every contact can have one or more contact points and every contact point has a normal impulse. This impulse is basically the force of the contact. We then determine the maximum impact force and determine if this should destroy the enemy.

If we determine we should destroy the enemy we add the body object to our set so we can destroy it later. Remember that, as Ray said on his tutorial, we can’t destroy the enemy’s body during the contact processing. So we hold this on our set and will process it later.

The value I used as the impulse that should destroy the enemy is something you’ll have to test for yourself. This will vary depending on the masses of the objects, the speed the bullet is impacting and some other factors. My suggestion is to start small and increase the value to determine what’s an acceptable value for your gameplay.

Now that we have our listener code let’s instantiate and use it. Go back to HelloWorldLayer.h and add the include of our new C++ class to the imports:

#import "MyContactListener.h"

And add a variable to hold our listener:

MyContactListener *contactListener;

No go to the implementation file and add this o the end of the init method:

contactListener = new MyContactListener();
world->SetContactListener(contactListener);

Now we have to add some code to actually destroy the enemies. Again this is going to be at the end of the tick method:

// Check for impacts
std::set<b2Body*>::iterator pos;
for(pos = contactListener->contacts.begin();
    pos != contactListener->contacts.end(); ++pos)
{
    b2Body *body = *pos;
    
    CCNode *contactNode = (CCNode*)body->GetUserData();
    [self removeChild:contactNode cleanup:YES];
    world->DestroyBody(body);
    
    [targets removeObject:[NSValue valueWithPointer:body]];
    [enemies removeObject:[NSValue valueWithPointer:body]];
}

// remove everything from the set
contactListener->contacts.clear();

We simply iterate though the set of our contact listener and destroy all the bodies and their associated sprites. We also remove them from the enemies and targets NSSet so we can determine if we have eliminated all the enemies.

Finally we clear the contact listener’s set to make it ready for the next time tick gets called and so that we don’t try to destroy those bodies again.

Go ahead and run the game. I promise you it’s even cooler now!

One dead dog!

But you can say there’s something missing here. Something that really tell us we destroyed those freaking enemies. So let’s add some polish to our game and make these little squirrel haters explode.

Cocos2d will make it very easy to do this using particles. I won’t go into a lot of details here since this topic can even have a tutorial of it’s own. If you want to dig deeper on this topic you can have a look at Chapter 14 of Rod and Ray’s Cocos2d book. In the meantime I’ll show you how to do this and how to try out other cocos2d built-in particles.

Let’s change the inside of the loop we just added to this:

b2Body *body = *pos;

CCNode *contactNode = (CCNode*)body->GetUserData();
CGPoint position = contactNode.position;
[self removeChild:contactNode cleanup:YES];
world->DestroyBody(body);

[targets removeObject:[NSValue valueWithPointer:body]];
[enemies removeObject:[NSValue valueWithPointer:body]];

CCParticleSun* explosion = [[CCParticleSun alloc] initWithTotalParticles:200];
explosion.autoRemoveOnFinish = YES;
explosion.startSize = 10.0f;
explosion.speed = 70.0f;
explosion.anchorPoint = ccp(0.5f,0.5f);
explosion.position = position;
explosion.duration = 1.0f;
[self addChild:explosion z:11];
[explosion release];

We first store the position of the enemy’s sprite so we know where to add the particle. Then we add an instance of CCParticleSun in this position. Pretty easy right? Go ahead and run the game!

Creating a particle effect with Cocos2D

Pretty cool, right?

CCParticleSun is one of many pre-configured CCParticleSystem sub-classes that comes with cocos2d. Command-click on CCParticleSun in Xcode and you’ll be taken to the CCParticlesExamples.m file. This file has a lot of sub-classes of CCParticleSystem that you can experiment with. And you may think that CCParticleExplosion would look better for us but you’d be wrong, at least in my opinion. But go ahead and try it out with a bunch of particle systems and see what happens.

One thing that I sneaked up on you is the texture file used on this particle. If you look inside the code for CCParticleSun you’ll notice it uses a file called fire.png. This file was already added to the project some time ago when we added all the image files.

The repository tag for this point in the tutorial is EnemiesExploding.

Contributors

Over 300 content creators. Join our team.