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

Contents

Hide contents

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 .

The squirrels are on a rampage!

The squirrels are on a rampage!

This is the second part of a two part tutorial series where we’ll build a cool catapult type game from scratch using Cocos2D and Box2D!

In the first part of the series, we added the catapult into the scene, with the ability to shoot dangerous acorns.

In this second and final part of the series, we’re going to flesh this out into a complete game, and add targets to shoot at and game logic.

If you don’t have it already, grab the sample project where we left it off in the last tutorial.

Without further ado, let’s get shooting!

Creating Targets

The targets creation won’t be anything very complicated as it’s mostly what you already know. Since we’re gonna have to add a bunch of targets let’s create a method to create one body and then we can call this method a bunch of times.

First let’s create a few variables to keep track of our new bodies. In the .h file add these variables:

NSMutableSet *targets;
NSMutableSet *enemies;

Go back to the implementation file and add the releases to the dealloc before we forget:

[targets release];
[enemies release];

Then add a helper method to create a target right above resetGame:

- (void)createTarget:(NSString*)imageName
          atPosition:(CGPoint)position
            rotation:(CGFloat)rotation
            isCircle:(BOOL)isCircle
            isStatic:(BOOL)isStatic
             isEnemy:(BOOL)isEnemy
{
    CCSprite *sprite = [CCSprite spriteWithFile:imageName];
    [self addChild:sprite z:1];
   
    b2BodyDef bodyDef;
    bodyDef.type = isStatic?b2_staticBody:b2_dynamicBody;
    bodyDef.position.Set((position.x+sprite.contentSize.width/2.0f)/PTM_RATIO,
                         (position.y+sprite.contentSize.height/2.0f)/PTM_RATIO);
    bodyDef.angle = CC_DEGREES_TO_RADIANS(rotation);
    bodyDef.userData = sprite;
    b2Body *body = world->CreateBody(&bodyDef);
   
    b2FixtureDef boxDef;
    if (isCircle)
    {
        b2CircleShape circle;
        circle.m_radius = sprite.contentSize.width/2.0f/PTM_RATIO;
        boxDef.shape = &circle;
    }
    else
    {
        b2PolygonShape box;
        box.SetAsBox(sprite.contentSize.width/2.0f/PTM_RATIO, sprite.contentSize.height/2.0f/PTM_RATIO);
        boxDef.shape = &box;
    }
   
    if (isEnemy)
    {
        boxDef.userData = (void*)1;
        [enemies addObject:[NSValue valueWithPointer:body]];
    }
   
    boxDef.density = 0.5f;
    body->CreateFixture(&boxDef);
   
    [targets addObject:[NSValue valueWithPointer:body]];
}

The method has a lot of parameters because we’ll have different types of targets and different ways of adding them to the scene. But don’t worry, it’s pretty simple, let’s go over it bit by bit.

First we load the sprite using the file name we pass to the method. To make it easier to position the objects the position we pass to the method is the position of the bottom left corner of where we want the target. Since the position Box2d uses is the center position we have to use the sprite’s size to set the position of the body.

We then define the body’s fixture depending on the desired shape we want. It can be a circle (for the enemies mostly) or a rectangle. Again the size of the fixture is derived from the sprite’s size.

Then, if this is an enemy (enemies will “explode” later on and I want to keep track of them to know if the level is over) I add it to the enemies set and set the fixture userData to 1. The userData is usually set to a struct or a pointer to another object but in this case we just want to “tag” these fixtures as being enemies’ fixtures. Why we need this will become clear when I show you how to detect if an enemy should be destroyed.

We then create the body’s fixture and add it to an array of targets so we can keep track of them.

Now it’s time to call this method a bunch of times to make complete our scene. This is a very big method because I have to call the createTarget method for every object I want to add on the scene.

Here are the sprites we’ll be using on the scene:

The targets we'll be shooting at

Now all we have to do is place these at the right positions. I got these by trial and error, but you can just use these premade positions! :] Add the following method after createTarget:

- (void)createTargets
{
    [targets release];
    [enemies release];
    targets = [[NSMutableSet alloc] init];
    enemies = [[NSMutableSet alloc] init];
    
    // First block
    [self createTarget:@"brick_2.png" atPosition:CGPointMake(675.0, FLOOR_HEIGHT) rotation:0.0f isCircle:NO isStatic:NO isEnemy:NO];
    [self createTarget:@"brick_1.png" atPosition:CGPointMake(741.0, FLOOR_HEIGHT) rotation:0.0f isCircle:NO isStatic:NO isEnemy:NO];
    [self createTarget:@"brick_1.png" atPosition:CGPointMake(741.0, FLOOR_HEIGHT+23.0f) rotation:0.0f isCircle:NO isStatic:NO isEnemy:NO];
    [self createTarget:@"brick_3.png" atPosition:CGPointMake(672.0, FLOOR_HEIGHT+46.0f) rotation:0.0f isCircle:NO isStatic:NO isEnemy:NO];
    [self createTarget:@"brick_1.png" atPosition:CGPointMake(707.0, FLOOR_HEIGHT+58.0f) rotation:0.0f isCircle:NO isStatic:NO isEnemy:NO];
    [self createTarget:@"brick_1.png" atPosition:CGPointMake(707.0, FLOOR_HEIGHT+81.0f) rotation:0.0f isCircle:NO isStatic:NO isEnemy:NO];
    
    [self createTarget:@"head_dog.png" atPosition:CGPointMake(702.0, FLOOR_HEIGHT) rotation:0.0f isCircle:YES isStatic:NO isEnemy:YES];
    [self createTarget:@"head_cat.png" atPosition:CGPointMake(680.0, FLOOR_HEIGHT+58.0f) rotation:0.0f isCircle:YES isStatic:NO isEnemy:YES];
    [self createTarget:@"head_dog.png" atPosition:CGPointMake(740.0, FLOOR_HEIGHT+58.0f) rotation:0.0f isCircle:YES isStatic:NO isEnemy:YES];
    
    // 2 bricks at the right of the first block
    [self createTarget:@"brick_2.png" atPosition:CGPointMake(770.0, FLOOR_HEIGHT) rotation:0.0f isCircle:NO isStatic:NO isEnemy:NO];
    [self createTarget:@"brick_2.png" atPosition:CGPointMake(770.0, FLOOR_HEIGHT+46.0f) rotation:0.0f isCircle:NO isStatic:NO isEnemy:NO];
    
    // The dog between the blocks
    [self createTarget:@"head_dog.png" atPosition:CGPointMake(830.0, FLOOR_HEIGHT) rotation:0.0f isCircle:YES isStatic:NO isEnemy:YES];
    
    // Second block
    [self createTarget:@"brick_platform.png" atPosition:CGPointMake(839.0, FLOOR_HEIGHT) rotation:0.0f isCircle:NO isStatic:YES isEnemy:NO];
    [self createTarget:@"brick_2.png"  atPosition:CGPointMake(854.0, FLOOR_HEIGHT+28.0f) rotation:0.0f isCircle:NO isStatic:NO isEnemy:NO];
    [self createTarget:@"brick_2.png"  atPosition:CGPointMake(854.0, FLOOR_HEIGHT+28.0f+46.0f) rotation:0.0f isCircle:NO isStatic:NO isEnemy:NO];
    [self createTarget:@"head_cat.png" atPosition:CGPointMake(881.0, FLOOR_HEIGHT+28.0f) rotation:0.0f isCircle:YES isStatic:NO isEnemy:YES];
    [self createTarget:@"brick_2.png"  atPosition:CGPointMake(909.0, FLOOR_HEIGHT+28.0f) rotation:0.0f isCircle:NO isStatic:NO isEnemy:NO];
    [self createTarget:@"brick_1.png"  atPosition:CGPointMake(909.0, FLOOR_HEIGHT+28.0f+46.0f) rotation:0.0f isCircle:NO isStatic:NO isEnemy:NO];
    [self createTarget:@"brick_1.png"  atPosition:CGPointMake(909.0, FLOOR_HEIGHT+28.0f+46.0f+23.0f) rotation:0.0f isCircle:NO isStatic:NO isEnemy:NO];
    [self createTarget:@"brick_2.png"  atPosition:CGPointMake(882.0, FLOOR_HEIGHT+108.0f) rotation:90.0f isCircle:NO isStatic:NO isEnemy:NO];
}

Pretty simple stuff – just calls our helper method for each target we want to add. Now add this to that bottom of the resetGame method:

[self createTargets];

If you run the project now you won’t be able to see this part of the scene unless you throw a bullet at it. So just until we have the scene figured out, let’s add a line to the end of the init method to make it easier for us to check of the scene is as we envisioned:

self.position = CGPointMake(-480, 0);

This will show us the right half of the scene instead of the left half. Now run the project and you should see the scene of targets!

Targets now in our scene!

You can play a little with the scene before moving on to the next step if you want. For example, comment out the 2 bricks at the right of the first block and run the project again and see what happens.

Now remove the line that changes the position from the init method and run again. Now throw a bullet at the targets and watch the destruction!

Acorn colliding into targets

Cool! The squirrel attack is underway!

Contributors

Over 300 content creators. Join our team.