How To Build a Monkey Jump Game Using Cocos2d 2.X, PhysicsEditor & TexturePacker – Part 2

In part 2 of this Monkey Jump tutorial series, you will add the hero to the game, make him move and jump, and start adding some gameplay. By .

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

Contents

Hide contents

How To Build a Monkey Jump Game Using Cocos2d 2.X, PhysicsEditor & TexturePacker – Part 2

30 mins

Push It!

Let's improve the gameplay and allow the monkey to push objects to the left and right - and give him the ability to put his hands above his head to shield him from dropping objects.

Do you remember how you added the sensors to the left and right sides of the monkey in Part One? They come into play now! The key to the sensors was setting up the Id parameter in PhysicsEditor. You are going to retrieve this value now!

But before you do that, you need to add a few instance variables to keep count of the left and right sensors, as well as the number of contacts made with the monkey's head. Add these variables to Monkey.h:

    int numPushLeftContacts;
    int numPushRightContacts;
    int numHeadContacts;

Next, the beginContactWith* and endContactWith* selectors have a contact parameter you can use to determine which part of the monkey has contact with an object – the Id value you added in PhysicsEditor is stored as user data in each fixture. So replace the existing object contact handlers for Monkey.mm with the following:

    -(void) beginContactWithObject:(GB2Contact*)contact
    {
        NSString *fixtureId = (NSString *)contact.ownFixture->GetUserData();
        if([fixtureId isEqualToString:@"push_left"])
        {
            numPushLeftContacts++;
        }
        else if([fixtureId isEqualToString:@"push_right"])
        {
            numPushRightContacts++;
        }
        else if([fixtureId isEqualToString:@"head"])
        {
            numHeadContacts++;
        }
        else
        {
            // count others as floor contacts 
            numFloorContacts++;        
        }
    }

    -(void) endContactWithObject:(GB2Contact*)contact
    {
        NSString *fixtureId = (NSString *)contact.ownFixture->GetUserData();
        if([fixtureId isEqualToString:@"push_left"])
        {
            numPushLeftContacts--;
        }
        else if([fixtureId isEqualToString:@"push_right"])
        {
            numPushRightContacts--;
        }
        else if([fixtureId isEqualToString:@"head"])
        {
            numHeadContacts--;
        }
        else
        {
            // count others as floor contacts 
            numFloorContacts--;        
        }
    }

As you see from the new code, you retrieve the Id, which You'll call fixtureId here, by accessing the contact parameter's fixture and then accessing the fixture's user data via the GetUserData method.

Now that we're tracking the contacts, you can update the monkey's animation frames to handle additional events.

Here's the decision table for the various animations:

Using the above table, you modify section #6 of updateCCFromPhysics in Monkey.mm as follows:

    // 6 - Update animation phase
    const float standingLimit = 0.1;
    NSString *frameName = nil;
    if((vX > -standingLimit) && (vX < standingLimit))
    {
        if(numHeadContacts > 0)
        {
            // Standing, object above head
            frameName = [NSString stringWithFormat:@"monkey/arms_up.png"];                        
        }
        else
        {
            // Just standing
            frameName = [NSString stringWithFormat:@"monkey/idle/2.png"];            
        }
    }
    else
    {
        if(numFloorContacts == 0)
        {
            // Jumping, in air
            frameName = [NSString stringWithFormat:@"monkey/jump/%@.png", dir];
        }
        else
        {
            // Determine if monkey is pushing an item
            bool isPushing =  (isLeft && (numPushLeftContacts > 0))
            || (!isLeft && (numPushRightContacts > 0));
            
            // On the floor
            NSString *action = isPushing ? @"push" : @"walk";
            
            frameName = [NSString stringWithFormat:@"monkey/%@/%@_%d.png", action, dir, animPhase];        
        }        
    }

Compile and test. Perfect! The monkey now behaves just as you want.

It Takes a Strong Monkey ...

Playing a little bit more though, I think the monkey should be a bit stronger under certain conditions. Currently, he's too weak to break free when an object is above his head. Let's give him some extra strength when he's trapped like that and wants to jump.

This is the current line from the jump selector in Monkey.mm that makes the monkey jump:

    [self applyLinearImpulse:b2Vec2(0,[self mass]*JUMP_IMPULSE) 
                       point:[self worldCenter]];   

Replace it with:

    float impulseFactor = 1.0;
    
    // if there is something above monkey's head make the push stronger
    if(numHeadContacts > 0)
    {
        impulseFactor = 2.5;
    }
    [self applyLinearImpulse:b2Vec2(0,[self mass]*JUMP_IMPULSE*impulseFactor) 
                       point:[self worldCenter]];

That's your monkey on steroids! He now uses a 2.5-times stronger impulse when there's an object resting above him – this should allow him to break free of most of the objects.

Let's also change the walking impulse in case the monkey needs to push an object to the side to break free. Go to updateCCFromPhysics and cut the following lines from section #6:

    // Determine if monkey is pushing an item
    bool isPushing =  (isLeft && (numPushLeftContacts > 0))
                   || (!isLeft && (numPushRightContacts > 0));

Now paste that code into section #4 and modify it as follows:

    // 4 - Determine direction of the monkey
    bool isLeft = (direction < 0);
    
    // Determine if monkey is pushing an item
    bool isPushing =  (isLeft && (numPushLeftContacts > 0))
                   || (!isLeft && (numPushRightContacts > 0));
    
    if((isLeft && (vX > -MAX_VX)) || ((!isLeft && (vX < MAX_VX))))
    {
        // apply the directional impulse
        float impulse = clamp(-[self mass]*direction*WALK_FACTOR, 
                              -MAX_WALK_IMPULSE, 
                              MAX_WALK_IMPULSE);       
        if(isPushing)
        {
            impulse *= 2.5;
        }
        [self applyLinearImpulse:-b2Vec2(impulse,0) point:[self worldCenter]];        
    }

Compile and test – that's much better. But there's still a problem: when the monkey slightly grazes an object with his head, the new jump power makes him go through the roof!

You need to clamp his maximum speed inside the updateCCFromPhysics method. Add this to the end of section #3 of the updateCCFromPhysics method:

    const float maxVelocity = 5.0;
    float v = velocity.Length();
    if(v > maxVelocity)
    {
        [self setLinearVelocity:maxVelocity/v*velocity];
    }

Notice that in the code above you are directly modifying values controlled by the Box2d engine, thus affecting the overall behavior of the physics engine. You should try to avoid doing this kind of manipulation.

Compile and test. I like the monkey's behavior now. He reacts quickly, and is strong enough to push objects but does not become uncontrollable.

Where To Go From Here?

If you don't have it already, here is all of the source code for this tutorial series.

You've now reached the end of Part Two of the MonkeyJump tutorial! The project in its current form is available in the source code zip in the folder called 5-MonkeyJumpAndRun.

Stay tuned for the final part of the series, where You'll add some performance improvements, add a HUD layer to the game, and yes - kill the monkey! :]

In the meantime, if you have any questions or comments, please join the forum discussion below!