How to Make a Game Like Cut the Rope – Part 2

Gustavo Ambrozio
This crocodile wants food!

This crocodile wants food!

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 .

Welcome back to our How to Make a Game Like Cut the Rope tutorial series!

In the first part of the series, you created the game’s basic scene and used Box2D’s b2RopeJoint and PatrickC’s VRope class to build a rope.

In this second and final part of the series, you’ll get to the best part – actually cuting that rope! :]

Getting Started: Cut the Verlet!

To cut the rope, you need to keep track of the user’s touches on screen and check if a touch intersects with a rope.

When the user drags his or her finger on the screen, the ccTouchesMoved:withEvent: method is called by Cocos2D, and you can use it to check the finger’s current position and last tracked position. Then you can check if the line formed by these two points intersects with any of the ropes on your scene.

To know which rope to cut and where, it helps to understand a bit more about how VRope works.

A VRope is composed of a series of VPoint objects that are connected using VStick objects. So, if you want to cut a VRope, the best way is to figure out if the line formed by the user’s finger movement intersects any of the VStick objects of any of the VRopes in your scene.

The first thing to do is to find a good algorithm to check if two lines intersect. The best one I found is explained very well on this page. You’ll implement this algorithm as a method in your HelloWorldLayer class.

Go ahead and add this to the end of HelloWorldLayer.mm:

-(BOOL)checkLineIntersection:(CGPoint)p1 :(CGPoint)p2 :(CGPoint)p3 :(CGPoint)p4
{
    // http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/
    CGFloat denominator = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);
 
    // In this case the lines are parallel so you assume they don't intersect
    if (denominator == 0.0f)
        return NO;
    CGFloat ua = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denominator;
    CGFloat ub = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denominator;
 
    if (ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0)
    {
        return YES;
    }
 
    return NO;
}

This method is an implementation of the equations from the above site, simplified for this project. Since you don’t care much about the intersection point, only whether or not the lines intersect, you don’t need to calculate x and y. You only need to check if ua and ub are within 0 and 1.

You’re almost ready to implement the ccTouchesMoved:withEvent: method. Before you do, expose a few instance variables of VRope and VPoint that you’ll need.

First you need to expose the sticks array of VRope. Open VRope.h and add this inside the interface block:

@property (nonatomic, readonly) NSArray *sticks;

Then synthesize the property in VRope.mm inside the implementation block:

@synthesize sticks = vSticks;

Now you need to expose the coordinates of the VPoint class. Add this to VPoint.h:

-(CGPoint)point;

And this to the end of VPoint.m:

-(CGPoint)point {
    return CGPointMake(x, y);
}

You’re almost ready now. Add the skeleton of ccTouchesMoved:withEvent: to the end of HelloWorldLayer.mm:

-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    static CGSize s = [[CCDirector sharedDirector] winSize];
 
    UITouch *touch = [touches anyObject];
    CGPoint pt0 = [touch previousLocationInView:[touch view]];
    CGPoint pt1 = [touch locationInView:[touch view]];
 
    // Correct Y axis coordinates to cocos2d coordinates
    pt0.y = s.height - pt0.y;
    pt1.y = s.height - pt1.y;
 
    for (VRope *rope in ropes)
    {
        for (VStick *stick in rope.sticks)
        {
            CGPoint pa = [[stick getPointA] point];
            CGPoint pb = [[stick getPointB] point];
 
            if ([self checkLineIntersection:pt0 :pt1 :pa :pb])
            {
                // Cut the rope here
                return;
            }
        }
    }
}

This is pretty simple. For every rope, you check each of the sticks used to create the rope. As soon as you find a stick that intersects the line dragged by the user, you cut the rope and stop the loop.

OK, so not that simple, as you still need to figure out how to cut the rope! :P

Let’s think about this for a few moments before you rush off to VRope again to add this functionality. A VRope is backed by a b2RopeJoint, and this rope joint in turn needs a body at each end. When you cut a VRope, you want to split it into two VRope objects (or create a new one), and for this you need another b2RopeJoint and two new bodies.

What? Two new bodies? Thinking in real world terms, this seems weird. When you cut a rope, two bodies do not suddenly come into existence.

That may be true, but in your little Box2D world, your rope is not really a rope! It’s just a logical binding between two bodies. So when you cut the rope in your world, to keep the illusion alive you’ll attach a very small, invisible object to each end of the cut, so the ropes maintain their weight and rope-like behavior.

Here’s a rough illustration of what you’re going to implement:

OK, it’s not a masterpiece of art, but you get the idea. :] The code and explanation below will make this even clearer.

On to the implementation then!

Go to VRope.h and add this method declaration right after -(void)reset:

-(VRope *)cutRopeInStick:(VStick *)stick newBodyA:(b2Body*)newBodyA newBodyB:(b2Body*)newBodyB;

You’ll feed this method two new bodies and the VStick where you want to cut the rope. Now let’s see how it’s done. Open VRope.mm and add this method right after reset:

-(VRope *)cutRopeInStick:(VStick *)stick newBodyA:(b2Body*)newBodyA newBodyB:(b2Body*)newBodyB {
 
    // 1-First, find out where in your array the rope will be cut
    int nPoint = [vSticks indexOfObject:stick];
 
    // Instead of making everything again you'll just use the arrays of
    // sticks, points and sprites you already have and split them
 
    // 2-This is the range that defines the new rope
    NSRange newRopeRange = (NSRange){nPoint, numPoints-nPoint-1};
 
    // 3-Keep the sticks in a new array
    NSArray *newRopeSticks = [vSticks subarrayWithRange:newRopeRange];
 
    // 4-and remove from this object's array
    [vSticks removeObjectsInRange:newRopeRange];
 
    // 5-Same for the sprites
    NSArray *newRopeSprites = [ropeSprites subarrayWithRange:newRopeRange];
    [ropeSprites removeObjectsInRange:newRopeRange];
 
    // 6-Number of points is always the number of sticks + 1
    newRopeRange.length += 1;
    NSArray *newRopePoints = [vPoints subarrayWithRange:newRopeRange];
    [vPoints removeObjectsInRange:newRopeRange];
 
    // 7-The removeObjectsInRange above removed the last point of
    // this rope that now belongs to the new rope. You need to clone
    // that VPoint and add it to this rope, otherwise you'll have a
    // wrong number of points in this rope
    VPoint *pointOfBreak = [newRopePoints objectAtIndex:0];
    VPoint *newPoint = [[VPoint alloc] init];
    [newPoint setPos:pointOfBreak.x y:pointOfBreak.y];
    [vPoints addObject:newPoint];
 
    // 7-And last: fix the last VStick of this rope to point to this new point
    // instead of the old point that now belongs to the new rope
    VStick *lastStick = [vSticks lastObject];
    [lastStick setPointB:newPoint];
    [newPoint release];
 
    // 8-This will determine how long the rope is now and how long the new rope will be
    float32 cutRatio = (float32)nPoint / (numPoints - 1);
 
    // 9-Fix my number of points
    numPoints = nPoint + 1;
 
    // Position in Box2d world where the new bodies will initially be
    b2Vec2 newBodiesPosition = b2Vec2(pointOfBreak.x / PTM_RATIO, pointOfBreak.y / PTM_RATIO);
 
    // Get a reference to the world to create the new joint
    b2World *world = newBodyA->GetWorld();
 
    // 10-Re-create the joint used in this VRope since bRopeJoint does not allow
    // to re-define the attached bodies
    b2RopeJointDef jd;
    jd.bodyA = joint->GetBodyA();
    jd.bodyB = newBodyB;
    jd.localAnchorA = joint->GetLocalAnchorA();
    jd.localAnchorB = b2Vec2(0, 0);
    jd.maxLength = joint->GetMaxLength() * cutRatio;
    newBodyB->SetTransform(newBodiesPosition, 0.0);
 
    b2RopeJoint *newJoint1 = (b2RopeJoint *)world->CreateJoint(&jd); //create joint
 
    // 11-Create the new rope joint
    jd.bodyA = newBodyA;
    jd.bodyB = joint->GetBodyB();
    jd.localAnchorA = b2Vec2(0, 0);
    jd.localAnchorB = joint->GetLocalAnchorB();
    jd.maxLength = joint->GetMaxLength() * (1 - cutRatio);
    newBodyA->SetTransform(newBodiesPosition, 0.0);
 
    b2RopeJoint *newJoint2 = (b2RopeJoint *)world->CreateJoint(&jd); //create joint
 
    // 12-Destroy the old joint and update to the new one
    world->DestroyJoint(joint);
    joint = newJoint1;
 
    // 13-Finally, create the new VRope
    VRope *newRope = [[VRope alloc] initWithRopeJoint:newJoint2
                                          spriteSheet:spriteSheet
                                               points:newRopePoints
                                               sticks:newRopeSticks
                                              sprites:newRopeSprites];
    return [newRope autorelease];
}

Wow, that’s a big one! I’ll try to explain everything done by the above code with the help of another masterpiece of illustration:

The illustration shows the VPoints and VSticks that make up the VRope. As you can see, the VRope has seven points and, therefore, six sticks. The indices of the points are indicated above them and the indices of the sticks under them.

In this example, the rope will be cut in the middle (nPoint = 3). The idea is to use the original VRope’s data so that the cut appears natural.

Let’s go though the code above, following the numbered comments:

  1. First determine the point at which the cut will be made. The passed stick object will be the first stick belonging to the new rope, so its index is the index of the point where the cut will happen. In the illustration above, this is 3.
  2. Define a range struct to indicate where you want your vSticks array to split. In the illustration, this range will have location=3 and length=3. This indicates the new rope will get sticks from 3 to 5.
  3. Create a new array with sticks that will go to the new rope.
  4. Remove these sticks from the current rope’s array.
  5. Do the same for the sprites that are used to draw the sticks.
  6. Extend the length of the range by one, because the number of points is always the number of sticks + 1. Create a new array with the points that will be in the new rope (points 3 to 6 in the illustration) and remove them from this object’s array.
  7. You probably noticed in the illustration that the breaking point of the rope was removed from this object’s array and will go to the new rope. To rectify this you need to “clone” this point and add it to this object’s points array. The last stick of this object also needs to be updated to point to the cloned end point.
  8. The cut ratio will be used to determine the length of the new ropes. In the illustration, this ratio will be 0.5.
  9. Fix the number of points of this object. In the illustration, it becomes 4.
  10. Recreate the joint that will be associated with this object. Attach bodyA and the newBodyB to this rope joint and use the old length and cut ratio to determine the new length. Also, place the newBodyB where the rope was cut.
  11. Create the new rope joint and attach the newBodyA and bodyB to it, also placing the newBodyA in the position of the cut.
  12. Then destroy the old joint and update the instance variable to the new joint.
  13. And finally, create the new VRope object using the arrays you collected before and the new joint. You still need to implement this init method, but it’s very simple and the code’s coming up next.

As promised, here’s the init method. Add this below the code you just added:

-(id)initWithRopeJoint:(b2RopeJoint*)aJoint 
           spriteSheet:(CCSpriteBatchNode*)spriteSheetArg
                points:(NSArray*)points 
                sticks:(NSArray*)sticks
               sprites:(NSArray*)sprites {
    if((self = [super init])) {
        joint = aJoint;
        spriteSheet = spriteSheetArg;
        vPoints = [[NSMutableArray alloc] initWithArray:points];
        vSticks = [[NSMutableArray alloc] initWithArray:sticks];
        ropeSprites = [[NSMutableArray alloc] initWithArray:sprites];
        numPoints = vPoints.count;
    }
    return self;
}

Yes, it’s pretty simple. It just keeps track of the arrays you passed in above and updates the numPoints instance variable.

You’ve probably noticed that you have a warning in your code about a method called setPointB not being found. Yes, I slipped it by you! I was hoping not to complicate things further while explaining the massive rope-cutting method. But this is very easy to fix.

Open VStick.h and add this declaration:

-(void)setPointB:(VPoint *)point;

Then open VStick.m and add this at the end:

-(void)setPointB:(VPoint *)point {
    pointB = point;
}

As you can see, just a simple setter method. Now your program should compile without any issues, and you can finally cut the rope!

Except… before you cut the rope, you need two new bodies. In HelloWorldLayer.mm, add the following new method to create these “tip” bodies, as I call them:

-(b2Body *) createRopeTipBody
{
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;
    bodyDef.linearDamping = 0.5f;
    b2Body *body = world->CreateBody(&bodyDef);
 
    b2FixtureDef circleDef;
    b2CircleShape circle;
    circle.m_radius = 1.0/PTM_RATIO;
    circleDef.shape = &circle;
    circleDef.density = 10.0f;
 
    // Since these tips don't have to collide with anything
    // set the mask bits to zero
    circleDef.filter.maskBits = 0;
    body->CreateFixture(&circleDef);
 
    return body;
}

The only thing worth pointing out here is the maskBits property. It’s set to zero so that it won’t collide with anything else in your world, and that’s how it should be since this is just a body you’re using to simulate the rope’s weight.

Now, go back to ccTouchesMove:withEvent: and add these lines before the return statement (where it says “Cut the rope here”):

                b2Body *newBodyA = [self createRopeTipBody];
                b2Body *newBodyB = [self createRopeTipBody];
 
                VRope *newRope = [rope cutRopeInStick:stick newBodyA:newBodyA newBodyB:newBodyB];
                [ropes addObject:newRope];

This applies everything you created above and cuts the selected rope at the correct point.

That’s enough code for now. Build and run, and you should be able to cut a rope with your finger (or mouse if in the simulator…):

If you feel as if you need a break, don’t worry, the fun and games are just beginning (pun intended). :]

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

A Pineapple-eating Crocodile? Why Not?

Now that the hard part is over, let’s add some gameplay and feed that pineapple to the crocodile.

You already have another sprite of the croc with its mouth open in your sprite sheet. What you’re going to do is make the croc open and close it’s mouth from time to time. The player will have to time the rope cuts so as to drop the pineapple over the croc’s mouth when it’s open. The croc decides when he’s hungry!

You need a way to check if the pineapple, as it drops, makes contact with the croc’s mouth. To do this, you’ll use Box2D’s contact listeners. You should be familiar with contact listeners from previous tutorials. If not, take a look at How To Create A Breakout Game with Box2D and Cocos2D – Part 2.

The first step is to add a body that will simulate the croc’s mouth, so the contact listener will have something to check for contact with the pineapple. Open HelloWorldLayer.h and add these lines:

    b2Body *crocMouth_;          // weak ref
    b2Fixture *crocMouthBottom_;    // weak ref

Then go to initPhysics in HelloWorldLayer.mm and add this at the end:

	// Define the croc's "mouth".
    b2BodyDef crocBodyDef;
    crocBodyDef.position.Set((s.width - croc_.textureRect.size.width)/PTM_RATIO, (croc_.position.y)/PTM_RATIO);
 
    crocMouth_ = world->CreateBody(&crocBodyDef);
 
    // Define the croc's box shape.
    b2EdgeShape crocBox;
 
    // bottom
    crocBox.Set(b2Vec2(5.0/PTM_RATIO,15.0/PTM_RATIO), b2Vec2(45.0/PTM_RATIO,15.0/PTM_RATIO));
    crocMouthBottom_ = crocMouth_->CreateFixture(&crocBox,0);
 
    crocMouth_->SetActive(NO);

You first create a static body with the origin on the bottom left corner of your sprite. You then create a fixture to simulate the croc’s mouth bottom (the one you’ll use in your collision detection). If you run the project, you can see the fixture in the right position:

You’ll be able to see it better once you open the croc’s mouth. You might wonder why the body (the physics body for the croc’s mouth, not it’s actual body :]) is set to be inactive (last line). This is because the croc’s mouth is initially closed. In that state, you don’t want the pineapple to interact with it.

Time to make the croc open its mouth. Add these two variables to HelloWorldLayer.h:

    BOOL crocMouthOpened;
    NSTimer *crocAttitudeTimer;

Now add this method to HelloWorldLayer.mm:

-(void)changeCrocAttitude
{
    crocMouthOpened = !crocMouthOpened;
    NSString *spriteName = crocMouthOpened ? @"croc_front_mouthopen.png" : @"croc_front_mouthclosed.png";
    [croc_ setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:spriteName]];
    [croc_ setZOrder:crocMouthOpened ? 1 : -1];
 
    crocMouth_->SetActive(crocMouthOpened);
 
    [crocAttitudeTimer invalidate];
    [crocAttitudeTimer release];
    crocAttitudeTimer = [[NSTimer scheduledTimerWithTimeInterval:3.0 + 2.0 * CCRANDOM_0_1() 
                                                          target:self 
                                                        selector:@selector(changeCrocAttitude) 
                                                        userInfo:nil 
                                                         repeats:NO] retain];
}

This method changes the boolean that indicates if the croc has its mouth open or not, changes the sprite to reflect this, sets the body to active if the mouth is open, and finally, starts a timer that will repeat this after a random amount of time between 3 and 5 seconds.

Notice that you’re also changing the croc’s sprite zOrder. This achieves the following behavior: When the croc’s mouth is open, if the pineapple drops, it will fall behind the croc (because the pineapple now has a zOrder of 0 and the croc a zOrder of 1), giving the illusion of having been eaten. If the croc’s mouth is closed, then the opposite happens and the pineapple falls in front of the croc. You’ll see this in action in a moment.

Before you forget, add this to dealloc in HelloWorldLayer.mm:

    [crocAttitudeTimer invalidate];
    [crocAttitudeTimer release];

And finally, make the initial call to changeCrocAttitude at the end of initLevel:

    crocMouthOpened = YES;
    [self changeCrocAttitude];

After the above call, changeCrocAttitude will itself take care of calling the method again at random intervals.

Great! Build and run the project and you should see the croc’s mouth open and close from time to time:

If you drop the pineapple in the croc’s mouth when it’s open, it will lay there for a while, but when the croc’s mouth closes, the pineapple drops into view. Don’t worry, you’ll fix this in a moment with a contact listener.

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

You Have Contact!

For the contact listener, you’ll mostly use code from the breakout game tutorial mentioned above. So begin by downloading the contact listener developed in that tutorial. Here are the include and implementation files.

Drag these files to your project tree. Be sure to check the “Copy items into destination group’s folder” to copy these files to your project folder.

Now, open HelloWorldLayer.h and add this include:

#import "MyContactListener.h"

And this to the class variables:

    MyContactListener *contactListener;

Open HelloWorldLayer.mm and add these lines at the end of initPhysics:

    // Create contact listener
    contactListener = new MyContactListener();
    world->SetContactListener(contactListener);

Add the following cleanup code to dealloc:

    delete contactListener;
    contactListener = NULL;

Now add this code to the end of update (don’t worry about the two warnings and an error that will appear, as you’ll fix those real quick):

    // Check for collisions
    bool shouldCloseCrocMouth = NO;
    std::vector<b2Body *>toDestroy;
    std::vector<MyContact>::iterator pos;
    for(pos = contactListener->_contacts.begin(); pos != contactListener->_contacts.end(); ++pos)
    {
        MyContact contact = *pos;
 
        bool hitTheFloor = NO;
        b2Body *potentialCandy = nil;
 
        // The candy can hit the floor or the croc's mouth. Let's check
        // what it's touching.
        if (contact.fixtureA == crocMouthBottom_)
        {
            potentialCandy = contact.fixtureB->GetBody();
        }
        else if (contact.fixtureB == crocMouthBottom_)
        {
            potentialCandy = contact.fixtureA->GetBody();
        }
        else if (contact.fixtureA->GetBody() == groundBody)
        {
            potentialCandy = contact.fixtureB->GetBody();
            hitTheFloor = YES;
        }
        else if (contact.fixtureB->GetBody() == groundBody)
        {
            potentialCandy = contact.fixtureA->GetBody();
            hitTheFloor = YES;
        }
 
        // Check if the body was indeed one of the candies
        if (potentialCandy && [candies indexOfObject:[NSValue valueWithPointer:potentialCandy]] != NSNotFound)
        {
            // Set it to be destroyed
            toDestroy.push_back(potentialCandy);
            if (hitTheFloor)
            {
                // If it hits the floor you'll remove all the physics of it and just simulate the pineapple sinking
                CCSprite *sinkingCandy = (CCSprite*)potentialCandy->GetUserData();
 
                // Sink the pineapple
                CCFiniteTimeAction *sink = [CCMoveBy actionWithDuration:3.0 position:CGPointMake(0, -sinkingCandy.textureRect.size.height)];
 
                // Remove the sprite and check if should finish the level.
                CCFiniteTimeAction *finish = [CCCallBlockN actionWithBlock:^(CCNode *node)
                                    {
                                        [self removeChild:node cleanup:YES];
                                        [self checkLevelFinish:YES];
                                    }];
 
                // Run the actions sequentially.
                [sinkingCandy runAction:[CCSequence actions:
                                         sink,
                                         finish,
                                         nil]];
 
                // All the physics will be destroyed below, but you don't want the
                // sprite do be removed, so you set it to null here.
                potentialCandy->SetUserData(NULL);
            }
            else
            {
                shouldCloseCrocMouth = YES;
            }
        }
    }
 
    std::vector<b2Body *>::iterator pos2;
    for(pos2 = toDestroy.begin(); pos2 != toDestroy.end(); ++pos2)
    {
        b2Body *body = *pos2;
        if (body->GetUserData() != NULL)
        {
            // Remove the sprite
            CCSprite *sprite = (CCSprite *) body->GetUserData();
            [self removeChild:sprite cleanup:YES];
            body->SetUserData(NULL);
        }
 
        // Iterate though the joins and check if any are a rope
        b2JointEdge* joints = body->GetJointList();
        while (joints)
        {
            b2Joint *joint = joints->joint;
 
            // Look in all the ropes
            for (VRope *rope in ropes)
            {
                if (rope.joint == joint)
                {
                    // This "destroys" the rope
                    [rope removeSprites];
                    [ropes removeObject:rope];
                    break;
                }
            }
 
            joints = joints->next;
            world->DestroyJoint(joint);
        }
 
        // Destroy the physics body
        world->DestroyBody(body);
 
        // Removes from the candies array
        [candies removeObject:[NSValue valueWithPointer:body]];
    }
 
    if (shouldCloseCrocMouth)
    {
        // If the pineapple went into the croc's mouth, immediately closes it.
        [self changeCrocAttitude];
 
        // Check if the level should finish
        [self checkLevelFinish:NO];
    }

This looks complicated, but it’s not, it’s just long. It’s a variation of the code used in the breakout game tutorial.

First you iterate through all the contacts. If any contact involves either the floor or the croc’s mouth, you keep a reference to the other body, as it might be a candy (in this project, it can’t be any other body, as the rope does not interact with either the body or the ground, but it’s better to include this check now, in case you add other elements to the game play later on). You then check the candies array, and if the potential candy body is there you can be sure it’s a candy.

If it is indeed a candy, you add it to the destruction vector. The difference is that, if it hit the croc’s mouth, you set a bool to true to make the croc’s mouth close. But if it hit the floor, then the croc’s mouth doesn’t need to close.

In the case of the candy hitting the floor, you’re going to add an animation to the sprite to simulate the candy sinking. At the end of the animation, you’ll force the level to end, since in this game you can only pass the level if the croc eats all the candy. (You’ll add this method shortly.)

After this first loop checks all the contacts, you go through the toDestroy vector to remove the physics bodies that need to be destroyed. One complication in your game is the removal of the VRope objects that might be associated with the destroyed object. So, for every destroyed object, you go though its joints and check if they belong to any VRope object. If they do, you remove the sprites from the scene and the VRope object from your ropes array.

Finally, you close the croc’s mouth if you had any mouth hits, and call a method (not yet written) to check if the level should end.

Now you should have two warnings about the checkLevelFinish method, and one error because the VRope object is not exposing the joint instance variable. Fix these problems now.

You’ll use another standard C++ class, so add this to the includes of HelloWorldLayer.mm:

#import <set>

Now add checkLevelFinish to HelloWorldLayer.mm:

-(void)checkLevelFinish:(BOOL)forceFinish
{
    if ([candies count] == 0 || forceFinish)
    {
        // Destroy everything
        [self finishedLevel];
 
        // Schedule a level restart 2 seconds from now
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC);
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            [self initLevel];
        });
    }
}
 
-(void) finishedLevel
{
    std::set<b2Body *>toDestroy;
 
    // Destroy every rope and add the objects that should be destroyed
    for (VRope *rope in ropes)
    {
        [rope removeSprites];
 
        // Don't destroy the ground body...
        if (rope.joint->GetBodyA() != groundBody)
            toDestroy.insert(rope.joint->GetBodyA());
        if (rope.joint->GetBodyB() != groundBody)
            toDestroy.insert(rope.joint->GetBodyB());
 
        // Destroy the joint already
        world->DestroyJoint(rope.joint);
    }
    [ropes removeAllObjects];
 
    // Destroy all the objects
    std::set<b2Body *>::iterator pos;
    for(pos = toDestroy.begin(); pos != toDestroy.end(); ++pos)
    {
        b2Body *body = *pos;
        if (body->GetUserData() != NULL)
        {
            // Remove the sprite
            CCSprite *sprite = (CCSprite *) body->GetUserData();
            [self removeChild:sprite cleanup:YES];
            body->SetUserData(NULL);
        }
        world->DestroyBody(body);
    }
 
    [candies removeAllObjects];
}

I slipped the implementation for finishedLevel in there too. :]

checkLevelFinish is simple enough. finishedLevel is also pretty simple, the only complication being that you have to destroy all the VRope objects first, and then all the objects that were associated with them, except for the ground body. These are all the “tip” objects you created when cutting the ropes and the remaining candies that might still exist.

You’re using a set instead of a vector because, when looping through all the ropes, a candy can be attached to more than one rope. In this case, if you used a vector you would add the same body twice and remove it twice in the destroy loop. A set solves this by not adding the same object twice.

In the destroy loop, you also have to destroy the body’s associated sprite, again, in case it’s a candy.

Now you only need to expose the joint instance variable in VRope, and you’re done!

Open VRope.h and add this after the declaration of cutRopeInStick:newBodyA:newBodyB::

@property (nonatomic, readonly) b2RopeJoint *joint;

Then add this to VRope.mm right after the #ifdef BOX2D_H line:

@synthesize joint = joint;

Cool! Run the project and play!

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

Adding More Candy

This has been fun, hasn’t it? You’ve come a long way in this tutorial! You can add some final touches to make it even better.

Go to initLevel in HelloWorldLayer.mm and add this block right before the block that moves the world forward a bit:

    // Add the candy
    b2Body *body2 = [self createCandyAt:CGPointMake(s.width * 0.5, s.height)];
 
    // Change the linear dumping so it swings more
    body2->SetLinearDamping(0.01);
 
    // Add a bunch of ropes
    [self createRopeWithBodyA:groundBody anchorA:cc_to_b2Vec(s.width * 0.65, s.height + 5)
                        bodyB:body2 anchorB:body2->GetLocalCenter()
                          sag:1.0];

Build and run, and you’ll see that this adds one more candy swinging from the top.

Another thing that can be easily improved is the quality of the rope. You may have noticed that sometimes the rope is a little unnatural and you can see the sticks. To improve this, you need to change one of the parameters of VRope.

Open VRope.mm and go to createRope:pointB:distance: and find this line:

	int segmentFactor = 12; //increase value to have less segments per rope, decrease to have more segments

If you decrease this number to, say, 6, your rope will have more VStick segments and will behave more naturally. Of course, the more you decrease it the longer it takes to perform the rope simulation, so there’s a performance tradeoff – test to see what works best for your app.

Another weird thing that happens is that the rope swings a lot and is a little unstable, swinging even after it’s been static for a while. You can tweak this too. Open VPoint.m and find applyGravity::

-(void)applyGravity:(float)dt {
    y -= 10.0f*dt; //gravity magic number
}

If you decrease this number (10.0), the rope will become “lighter” and move more naturally. Try changing it to 1.0.

One thing that might still happen is a contact between the pineapple and the top edge of the world, which looks kinda weird – the pineapple colliding with the sky! So remove the top edge, along with the left and right edges, and increase the bottom edge a bit to widen your world. :]

Open HelloWorldLayer.mm and go to initPhysics. Remove these lines:

    // top
    groundBox.Set(b2Vec2(0,s.height/PTM_RATIO), b2Vec2(s.width/PTM_RATIO,s.height/PTM_RATIO));
    groundBody->CreateFixture(&groundBox,0);
 
    // left
    groundBox.Set(b2Vec2(0,s.height/PTM_RATIO), b2Vec2(0,0));
    groundBody->CreateFixture(&groundBox,0);
 
    // right
    groundBox.Set(b2Vec2(s.width/PTM_RATIO,s.height/PTM_RATIO), b2Vec2(s.width/PTM_RATIO,0));
    groundBody->CreateFixture(&groundBox,0);

And change this line:

    groundBox.Set(b2Vec2(0,0), b2Vec2(s.width/PTM_RATIO,0));

To:

    groundBox.Set(b2Vec2(-s.width/PTM_RATIO,0), b2Vec2(2*s.width/PTM_RATIO,0));

Finally, some memory management fixes to VRope. If you use Xcode’s Analyze on the project, you’ll see some potential problems with memory management: unusual ways of doing things that could become a problem. In fact, they actually have become a problem, since you’re releasing the VPoint created in cutRopeInStick:bodyA:bodyB:, and it releases it again in the dealloc method.

To make the class follow best practices, open VRope.mm, find createRope:pointB:distance: and change it to add the two release statements (indicated via comments) below:

 
    for(int i=0;i&lt;numPoints;i++) {
        CGPoint tmpVector = ccpAdd(pointA, ccpMult(ccpNormalize(diffVector),multiplier*i*(1-antiSagHack)));
        VPoint *tmpPoint = [[VPoint alloc] init];
        [tmpPoint setPos:tmpVector.x y:tmpVector.y];
        [vPoints addObject:tmpPoint];
 
        // Add this line:
        [tmpPoint release];
    }
    for(int i=0;i&lt;numPoints-1;i++) {
        VStick *tmpStick = [[VStick alloc] initWith:[vPoints objectAtIndex:i] pointb:[vPoints objectAtIndex:i+1]];
        [vSticks addObject:tmpStick];
 
        // Add this line:
        [tmpStick release];
    }

Now go to dealloc and remove these lines:

    for(int i=0; i < numPoints; i++) {
        [[vPoints objectAtIndex:i] release];
        if(i!=numPoints-1)
            [[vSticks objectAtIndex:i] release];
    }

And that’s it, memory problems solved and analyzer happy!

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

Gratuitous Music and Sound Effects

The game is pretty cool as it is, but it’s a little quiet for my tastes. Add some sounds and background music to liven it up. :]

I’ve selected a nice jungle song from incompetech.com and some sound effects from freesound.org. You can select your own, or get the ones I picked out here.

Extract the archive and drag it to the Resources folder of your project:

Be sure to check the “Copy items into destination group’s folder” to copy these files to your project folder.

Then, add the following import and constants to the top of your HelloWorldLayer.mm:

#import "SimpleAudioEngine.h"
 
#define kCuttingSound       @"cut.caf"
#define kBiteSound          @"bite.caf"
#define kSplashSound        @"splash.caf"
#define kBackgroundMusic    @"CheeZeeJungle.caf"

Now, add this code to init, just before the scheduleUpdate call:

        [[SimpleAudioEngine sharedEngine] preloadEffect:kCuttingSound];
        [[SimpleAudioEngine sharedEngine] preloadEffect:kBiteSound];
        [[SimpleAudioEngine sharedEngine] preloadEffect:kSplashSound];
        [[SimpleAudioEngine sharedEngine] playBackgroundMusic:kBackgroundMusic loop:YES];
        [SimpleAudioEngine sharedEngine].backgroundMusicVolume = 0.4;

This preloads all your effect audio files. It also starts playing the background music and sets it to automatically loop when it ends. The last line lowers the volume of the background music a bit, so that it doesn’t drown out the other sounds.

Now you just need to add the effects at some key places.

The cutting sound should play when the user cuts the rope. As you remember, this happens in ccTouchesMoved: in an “if” condition inside two for loops. Find ccTouchedMoved:withEvent: and add this right before the return statement, inside the “if” condition:

 
                [[SimpleAudioEngine sharedEngine] playEffect:kCuttingSound];

The bite sound should go at the end of update, inside the last if condition where you force the croc’s mouth to close. Add this inside the if, right after the call to changeCrocAttitude:

        // Play sound effect
        [[SimpleAudioEngine sharedEngine] playEffect:kBiteSound];

Last but not least is the splash sound for the pineapple falling into the water. This also happens inside update. Remember when you added the code to make the pineapple sink? The sound effect should be triggered there. Find if (hitTheFloor) inside update and add this inside the if condition (at the top):

                [[SimpleAudioEngine sharedEngine] playEffect:kSplashSound];

And that’s it! Run and test it out. It’s amazing how a few lines of code and some sound effects can change a game!

Knowing When Its Over

It’s over when it’s over, but you should display some text to let the user know if they’ve fed the crocodile and won, or disappointed the crocodile and lost.

First, there’s the case of when the user lost because a piece of candy fell in the water. Add the code in the same location that you added the splash sound, in update, right after the splash sound call:

                CGSize s = [[CCDirector sharedDirector] winSize];
                CCLabelTTF *loseLabel = [[CCLabelTTF alloc] initWithString:@"Try Again!" 
                                                                dimensions:s
                                                                hAlignment:kCCTextAlignmentCenter
                                                                vAlignment:kCCVerticalTextAlignmentCenter
                                                                  fontName:@"Verdana-Bold"
                                                                  fontSize:60.0];
                loseLabel.color = ccc3(255, 0, 0);
                loseLabel.anchorPoint = CGPointZero;
                [self addChild:loseLabel];

This adds a label to the center of the screen, in red.

To make the message go away, add a call to remove it just a few lines below, inside the finish block (just add the line indicated with a comment, not the whole block):

                // Remove the sprite and check if should finish the level.
                CCFiniteTimeAction *finish = [CCCallBlockN actionWithBlock:^(CCNode *node)
                                    {
                                        [self removeChild:node cleanup:YES];
                                        [self checkLevelFinish:YES];
                                        // add this line:
                                        [self removeChild:loseLabel cleanup:YES];
                                    }];

If the user won by feeding all the candy to the crocodile, let them know by adding this at the end of the “if” condition inside checkLevelFinish (after the other existing code in the condition):

        if ([candies count] == 0 && !forceFinish)
        {
            CGSize s = [[CCDirector sharedDirector] winSize];
            CCLabelTTF *winLabel = [[CCLabelTTF alloc] initWithString:@"Level Finished!" 
                                                           dimensions:s
                                                           hAlignment:kCCTextAlignmentCenter
                                                           vAlignment:kCCVerticalTextAlignmentCenter
                                                             fontName:@"Verdana-Bold"
                                                             fontSize:60.0];
            winLabel.color = ccc3(255, 0, 0);
            winLabel.anchorPoint = CGPointZero;
            [self addChild:winLabel];
            dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
                [self removeChild:winLabel cleanup:YES];
            });
        }

That’s it! Run and try to make the croc eat the two candies! That candy at the top is tricky, isn’t it? The croc really makes me mad sometimes….

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

Where to Go From Here?

Well, that was fun! Hard, but fun! Now go and make this game even better. I’m sure that are lots of ways to improve it. So, fork the project on github, make it better and add a pull request. I’d love to see what you’re capable of!


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 .

Gustavo Ambrozio

Gustavo is a software engineer from Brazil with over 20 years experience, over 4 years of iOS experience and founder of CodeCrop Software. Gustavo has worked on everything from server software, corporate systems and now is having fun living in sunny California developing mobile games for PocketGems. You can also read his blog, follow him on Twitter, GitHub and connect on LinkedIn.

User Comments

4 Comments

  • Hello,
    I have enjoyed this tutorial very much.As usual another great tutorial from this site which helped me a lot to learn cocos2d.I want to remove rope after cutting..I have tried following ways..

    1.In touchesmove
    didn't added newRope to ropes array-(But that dont remove rope.)
    2.In cutRopeInStick of vrope.m
    tried to call removeSprites at end of method and also tried to remove new joint (not working)

    Is anybody know what should be the proper way..I am new to cocos2d..
    virajdasondi
  • The rope is not together?? Each section of the middle gap what can i do for it to together it which has not gap
    a6965921
  • Hello ,

    can you said how to add more levels there.?
    rollstone
  • Hi! Why you didn't call timer periodically from init method with parameter "repeats:YES" ? Thanks for answer.
    andrei0077

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Vote for Our Next Tutorial!

Every week, we alternate between Gaming and Non-Gaming tutorial votes. This week: Non-Gaming!

    Loading ... Loading ...

Last week's winner: How to Make a Simple 2D Game with Metal.

Suggest a Tutorial - Past Results

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in October: Xcode 6 Tips and Tricks!

Sign Up - October

Our Books

Our Team

Tutorial Team

  • Dominik Hauser
  • Kyle Richter

... 53 total!

Update Team

... 14 total!

Editorial Team

... 22 total!

Code Team

  • Orta Therox

... 3 total!

Subject Matter Experts

  • Richard Casey

... 4 total!