How To Make A Game Like Fruit Ninja With Box2D and Cocos2D – Part 1

This is a post by iOS Tutorial Team Member Allen Tan, an iOS developer and co-founder at White Widget. You can also find him on Google+ and Twitter. In this tutorial, you’ll learn how to make a sprite cutting game for the iPhone similar to Fruit Ninja by Halfbrick Studios using the powerful Cocos2D and […] By Allen Tan.

Leave a rating/review
Save for later
Share

This is a post by iOS Tutorial Team Member Allen Tan, an iOS developer and co-founder at White Widget. You can also find him on and Twitter.

In this tutorial, you’ll learn how to make a sprite cutting game for the iPhone similar to Fruit Ninja by Halfbrick Studios using the powerful Cocos2D and Box2D libraries along with some pre-made tools.

In most slicing games, when you draw a cut line through a sprite, the game typically converts the sprite image into two pre-drawn sprite images with the slice always down the middle, regardless of where you actually cut.

But this tutorial will demonstrate an even cooler technique. Our fruits will be able to be cut multiple times, and they will be divided dynamically based on the exact cut lines!

As you might imagine, this is an advanced technique, so this tutorial is for advanced Cocos2D and Box2D developers. If you are new to Cocos2D or Box2D, you should go through (at the least) the intro to Cocos2D and intro to Box2D tutorials first before proceeding with this tutorial.

This tutorial series is split into three parts:

  • In this first part of the series, you will lay the foundations for the game, and learn how to create textured polygons.
  • The second part will show you how to slice & split these textured polygons.
  • The third part will show you how to turn this into a complete game by adding gameplay and effects.

I would like to give special thanks to Rick Smorawski for laying the foundations for the project this tutorial is based on. He was responsible for porting this flash-based slicing demo into Cocos2D, and also for porting CCBlade and PRKit to Cocos2D 2.0.

Keep reading to check out the video of what you’ll make and to get started learning some cool new techniques!

Game Demo

Here’s a demo video showing you what you’ll make in this tutorial series:

As I mentioned, you’ll see that the fruit cutting effect is really dynamic. The fruit is cut dynamically based on where you slice, and since you can slice objects multiple times, you can really chop things up!

You can see that you’ll also implement a cool slicing trail effect, some particle systems, gameplay logic, and sounds to spice things up.

There’s a lot to cover – so let’s get started!

Getting Started: Project Setup

You’re going to use Cocos2D 2.X in this project, so go ahead and download it if you don’t have it already. Note that you are free to use Cocos2D 1.X instead of 2.X. With 1.X, you can skip the parts about converting PRKit and CCBlade to Cocos2D 2.X, but be sure to pay attention to the other small changes you make to those classes.

After downloading, double click the tar to unarchive it, then install the templates with the following commands in the Terminal:

cd ~/Downloads/cocos2d-iphone-2.0-beta
./install-templates.sh -f -u

Start up Xcode and create a new project with the iOS\cocos2d v2.x\cocos2d iOS with Box2d template and name it CutCutCut.

Your new project should look something like this:

Project Start

First things first – you should clean up the template a bit to get to a good starting point.

Open HelloWorldLayer.h and remove the following line:

CCTexture2D *spriteTexture_;// weak ref

Switch to HelloWorldLayer.mm and make the following changes

// Remove this line from the top
#import "PhysicsSprite.h"

// Replace the init method with this
-(id) init
{
    if( (self=[super init])) {
        // enable events
        self.isTouchEnabled = YES;
        self.isAccelerometerEnabled = YES;
        CGSize s = [CCDirector sharedDirector].winSize;
        
        // init physics
        [self initPhysics];
        
        [self scheduleUpdate];
    }
    return self;
}

// Remove these two methods
-(void) createMenu {
    //all content
}
-(void) addNewSpriteAtPosition:(CGPoint)p methods {
    //all content
}

// Remove this line from ccTouchesEnded
[self addNewSpriteAtPosition: location];

At this point, you’ve removed all references to PhysicsSprite from HelloWorldLayer, but don’t remove the files from the project quite yet. Later on, you’ll need to copy a method that PhysicsSprite.mm contains somewhere else, so leave it put for now.

Hit Command+R to compile and run your project, and you should see a blank screen with a green border around it:

Clean Slate

The remaining template code has set up Box2D debug drawing, which draws borders around the Box2D bodies on the screen. See the thin green lines drawn around the screen? Those are the walls generated by the default initPhysics method that came with the template.

Take a look at the remaining template code to make sure you understand what’s going on so far – it initializes a Box2D world, sets up the ground (green borders), sets up debug drawing, etc. This is a pretty good “almost blank” starting point with Box2D we can build on from here.

Resource Kit

Next download the resources for this project and unzip the file.

Don’t add everything into the project just yet; some of the files are actually optional. Keep the folder handy though – as you go through the tutorial, from time to time I will ask you to add some of these files into the project.

Here’s what you will find inside:

  • A background image and a bunch of fruit art made by Vicki, and other miscellaneous images in the Images folder
  • The background sound mix made using gomix.it in the Sounds folder
  • Sound effects made using bfxr or downloaded from freesound in the Sounds folder
  • All the particle systems created with Particle Designer in the Particles folder
  • A PLIST file generated by PhysicsEditor containing vertex information for the Fruits & Bomb classes in the Misc folder
  • Fruits & Bomb classes in the Classes folder
  • The versions of PRKit and CCBlade you will use in the tutorial in the Classes folder
  • An attribution list for resources that are under the Attribution License in the Misc folder

Drawing Textured Polygons with PRKit

Our goal is to cut sprites into multiple pieces. A typical CCSprite contains a texture, and a bounding box no matter what shape the image is. This is not suitable for our game since knowing the actual shapes within the images is a crucial step to creating sprites that can be cut, sliced, and split.

You need to create Textured Polygons, which:

  • Create a correspondence between a polygon/shape and an image (Texture Mapping)
  • Show only the parts of the image that are within the bounds of the polygon (Texture Filling)

Neither Cocos2D nor Box2D comes with a built-in class that handles the custom features you want, and normally, this would take some triangulation coupled with custom OpenGL drawing code.

Sounds hard right?

Fortunately, all the complicated calculations and drawing code needed to achieve this have already been written by the good folks at Precognitive Research. They created an addition to the Cocos2D library named PRKit, which handles Texture Mapping and Filling.

To get started on Textured Polygons, download PRKit, extract it, and drag the PRKit folder into to your project. Make sure that “Copy items into destination group’s folder” is checked and “Create groups for any added folders” is selected.

Note that PRKit is maintained by Precognitive Research, so it may get updated in time. To avoid confusion, our resource kit also contains the exact version of PRKit I used in making this tutorial.

Your project should now include these files:

PRKit Yey!

Compile and run, and you will encounter a few errors:

PRKit Needs to be Converted

The errors pop up because PRKit was made for Cocos2D 1.X, which uses OpenGL ES 1.1, whereas you are using Cocos2D 2.X, which uses OpenGL ES 2.0, and there are significant differences between the two.

To fix this, open PRFilledPolygon.m and make these changes:

// Add inside the initWithPoints: andTexture: usingTriangulator: method
self.shaderProgram = [[CCShaderCache sharedShaderCache] programForKey:kCCShader_PositionTexture];

// Replace the calculateTextureCoordinates method with this
-(void) calculateTextureCoordinates {
    for (int j = 0; j < areaTrianglePointCount; j++) {
        textureCoordinates[j] = ccpMult(areaTrianglePoints[j],1.0f/texture.pixelsWide*CC_CONTENT_SCALE_FACTOR());
        textureCoordinates[j].y = 1 - textureCoordinates[j].y;
    }
}

// Replace the draw method with this
-(void) draw{
    CC_NODE_DRAW_SETUP();
    
    ccGLBindTexture2D( self.texture.name );
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    
    ccGLBlendFunc( blendFunc.src, blendFunc.dst);
    
    ccGLEnableVertexAttribs( kCCVertexAttribFlag_Position | kCCVertexAttribFlag_TexCoords );
    
    glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, areaTrianglePoints);
    glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, 0, textureCoordinates);
    
    glDrawArrays(GL_TRIANGLES, 0, areaTrianglePointCount);
}

Let's go over these changes bit by bit.

First, in Cocos2D every CCNode has an OpenGL ES 2.0 shader program attached to it. To draw the PRFilledPolygon, you need to sue the built-in "Position/Texture" shader, which you assign in the init method.

Next, you need to set up the correct texture coordinates for each point in the polygon. To do this, you have to make two changes the calculateTextureCoordinates method:

  • Scale: Since this class does its own calculations on the coordinates of its texture, it does not automatically handle the retina display. To fix this, you just multiply texture.pixelsWide with CC_CONTENT_SCALE_FACTOR - a convenient multiplier value provided by Cocos2D for converting values between regular and retina equivalents.
  • Flip Y: For some reason, PRFIlledPolygon draws textures upside down, so you simply flip the y value here.

Last, the drawing code is updated to OpenGL ES 2.0, in a similar fashion to how CCSprite drawing changed from Cocos2D 1.X to Cocos2D 2.X:

  • Start by calling CC_NODE_DRAW_SETUP() to prepare the node for drawing.
  • Calls to glDisableClientState() and glEnableClientState() are obsolete and are discarded.
  • The glVertexPointer() and glTexCoordPointer() commands are both replaced by glVertexAttribPointer(), which now accepts Vertex Position or Texture Coordinate as its first option.
  • The setup of glTexEnvf(), which was responsible for repeating the sprite in case the polygon was bigger than the texture, is replaced by calls to glTexParameteri().

If you're confused by any of this, you might want to check out our OpenGL ES 2.0 for iPhone and Custom Cocos2D 2.X Shaders tutorials for more background information. But you don't need to worry about it too much, because at this point all we're doing is porting the class to work in Cocos2D 2.X :]

Compile and run, and all PRKit errors should disappear!

It's time to put PRKit in action. You will be sub-classing PRKit's PRFilledPolygon class to make a base PolygonSprite class that will draw our fruits.

The PolygonSprite builds on PRFilledPolygon by attaching a Box2D body to the sprite, and it will also contain other custom variables and methods for the fruits in our game implementation.

Let's get to it. Hit Command+N and create a new file with the iOS\cocos2d v2.x\CCNode Class template. Make it a subclass of PRFilledPolygon and name it PolygonSprite.m.

Switch over to PolygonSprite.h and make the following changes:

// Add to top of file
#import "Box2D.h"
#import "PRFilledPolygon.h"
#define PTM_RATIO 32

// Add inside @interface
b2Body *_body;
BOOL _original;
b2Vec2 _centroid;

// Add after the @interface
@property(nonatomic,assign)b2Body *body;
@property(nonatomic,readwrite)BOOL original;
@property(nonatomic,readwrite)b2Vec2 centroid;

// Add before the @end
-(id)initWithTexture:(CCTexture2D*)texture body:(b2Body*)body original:(BOOL)original;
-(id)initWithFile:(NSString*)filename body:(b2Body*)body original:(BOOL)original;
+(id)spriteWithFile:(NSString*)filename body:(b2Body*)body original:(BOOL)original;
+(id)spriteWithTexture:(CCTexture2D*)texture body:(b2Body*)body original:(BOOL)original;
-(id)initWithWorld:(b2World*)world;
+(id)spriteWithWorld:(b2World*)world;
-(b2Body*)createBodyForWorld:(b2World*)world position:(b2Vec2)position rotation:(float)rotation vertices:(b2Vec2*)vertices vertexCount:(int32)count density:(float)density friction:(float)friction restitution:(float)restitution;
-(void)activateCollisions;
-(void)deactivateCollisions;

The above code declares the base variables and methods you need to create a PolygonSprite. These are:

  • body: This is the Box2D body that is attached to our sprite. It is needed for physics simulation.
  • original: Complete and sliced sprites will use the same PolygonSprite class, as such, differentiating between the two will be important. If this is YES, it means that it is the uncut, or original object that you created, otherwise, it is just a piece of the whole.
  • centroid: The center of the polygon within the image won't always be the same as the center of the image, so it is useful to store this value.
  • properties: Expose all the variables using properties so that other classes can access them freely.
  • init/spriteWith*: Our main init methods following the same naming convention as Cocos2D.
  • other methods: These are methods creating & dealing with the attached Box2D body and its properties.
  • PTM_RATIO: Pixels to Meters ratio. Box2D needs this conversion value because it deals with Meters instead of Pixels.

Quickly switch to PolygonSprite.m and rename it PolygonSprite.mm. All classes that mix Objective-C (Cocos2D) and C++ (Box2D) code need to have a ".mm" extension to notify the compiler of the mixed syntax.

Next, make the following changes to PolygonSprite.mm:

// Add inside the @implementation
@synthesize body = _body;
@synthesize original = _original;
@synthesize centroid = _centroid;

+(id)spriteWithFile:(NSString *)filename body:(b2Body *)body  original:(BOOL)original
{
    return [[[self alloc]initWithFile:filename body:body original:original] autorelease];
}

+(id)spriteWithTexture:(CCTexture2D *)texture body:(b2Body *)body  original:(BOOL)original
{
    return [[[self alloc]initWithTexture:texture body:body original:original] autorelease];
}

+(id)spriteWithWorld:(b2World *)world
{
    return [[[self alloc]initWithWorld:world] autorelease];
}

-(id)initWithFile:(NSString*)filename body:(b2Body*)body  original:(BOOL)original
{
    NSAssert(filename != nil, @"Invalid filename for sprite");
    CCTexture2D *texture = [[CCTextureCache sharedTextureCache] addImage: filename];
    return [self initWithTexture:texture body:body original:original];
}

-(id)initWithTexture:(CCTexture2D*)texture body:(b2Body*)body original:(BOOL)original
{
    // gather all the vertices from our Box2D shape
    b2Fixture *originalFixture = body->GetFixtureList();
    b2PolygonShape *shape = (b2PolygonShape*)originalFixture->GetShape();
    int vertexCount = shape->GetVertexCount();
    NSMutableArray *points = [NSMutableArray arrayWithCapacity:vertexCount];
    for(int i = 0; i < vertexCount; i++) {
        CGPoint p = ccp(shape->GetVertex(i).x * PTM_RATIO, shape->GetVertex(i).y * PTM_RATIO);
        [points addObject:[NSValue valueWithCGPoint:p]];
    }
    
    if ((self = [super initWithPoints:points andTexture:texture]))
    {
        _body = body;
        _body->SetUserData(self);
        _original = original;
        // gets the center of the polygon
        _centroid = self.body->GetLocalCenter();
        // assign an anchor point based on the center
        self.anchorPoint = ccp(_centroid.x * PTM_RATIO / texture.contentSize.width, 
                               _centroid.y * PTM_RATIO / texture.contentSize.height);
        // more init stuff here later when you expand PolygonSprite
    }
    return self;
}

-(id)initWithWorld:(b2World *)world
{
    //nothing to do here
    return nil;
}

Similar to Cocos2D, all the spriteWith* methods are just autorelease counterparts of the initWith* methods, while initWithWorld has no actual use for this class yet, but instead it will be used by PolygonSprite's subclasses later.

The bulk of the changes can be found in the initWithFile and initWithTexture methods. To get the flow of things, creating a fruit will be called in this sequence:

Init Sequence

  • initWithWorld: This is intended for subclasses of PolygonSprite so you do nothing except return nil, and deal with it later.
  • initWithFile: This adds the texture from our file and passes everything to initWithTexture.
  • initWithTexture: Our main initialization. PRFilledPolygon needs a texture and all the vertices of the polygon it fills. Since the previous step already handled the texture part, this step handles the vertices by collecting them from the sprite's Box2D body. After passing them to PRFilledPolygon, it proceeds to initialize the variables that you have previously declared.
  • initWithPoints: Everything this does is contained within PRKit and the good thing is that you don't really need to touch PRKit anymore now that you have updated its code.

Still inside PolygonSprite.mm, add the following methods:

-(void)setPosition:(CGPoint)position
{
    [super setPosition:position];
    _body->SetTransform(b2Vec2(position.x/PTM_RATIO,position.y/PTM_RATIO), _body->GetAngle());
}

-(b2Body*)createBodyForWorld:(b2World *)world position:(b2Vec2)position rotation:(float)rotation vertices:(b2Vec2*)vertices vertexCount:(int32)count density:(float)density friction:(float)friction restitution:(float)restitution
{
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;
    bodyDef.position = position;
    bodyDef.angle = rotation;
    b2Body *body = world->CreateBody(&bodyDef);
    
    b2FixtureDef fixtureDef;
    fixtureDef.density = density;
    fixtureDef.friction = friction;
    fixtureDef.restitution = restitution;
    fixtureDef.filter.categoryBits = 0;
    fixtureDef.filter.maskBits = 0;
    
    b2PolygonShape shape;
    shape.Set(vertices, count);
    fixtureDef.shape = &shape;
    body->CreateFixture(&fixtureDef);
    
    return body;
}

-(void)activateCollisions
{
    b2Fixture *fixture = _body->GetFixtureList();
    b2Filter filter = fixture->GetFilterData();
    filter.categoryBits = 0x0001;
    filter.maskBits = 0x0001;
    fixture->SetFilterData(filter);
}

-(void)deactivateCollisions
{
    b2Fixture *fixture = _body->GetFixtureList();
    b2Filter filter = fixture->GetFilterData();
    filter.categoryBits = 0;
    filter.maskBits = 0;
    fixture->SetFilterData(filter);
}

In the above code, you first overload the setPosition method of CCNode so that when you update the sprite's position, the associated Box2D body's position gets updated as well.

You then make a convenience method for creating and defining a Box2D body. To create a body, you need to define a body definition, a body object, a shape, and a fixture definition. No real hard values are being assigned here yet since this method will be used by subclasses of PolygonSprite later on.

The only thing to note is the categoryBits and maskBits. These two are used for filtering collisions between objects such that if a category bit of an object matches a mask bit of another object and vice versa, there will be a collision between those two objects. You set these to 0 first because you don't want any collisions happening when the objects are first initialized.

Lastly, you define two methods that simply replace the categoryBits and maskBits so that you can activate and deactivate the collisions of our PolygonSprites.

There is one more thing to add to PolygonSprite.mm:

-(CGAffineTransform) nodeToParentTransform
{
    b2Vec2 pos  = _body->GetPosition();
    
    float x = pos.x * PTM_RATIO;
    float y = pos.y * PTM_RATIO;
    
    if ( !isRelativeAnchorPoint_ ) {
        x += anchorPointInPoints_.x;
        y += anchorPointInPoints_.y;
    }
    
    // Make matrix
    float radians = _body->GetAngle();
    float c = cosf(radians);
    float s = sinf(radians);
    
    if( ! CGPointEqualToPoint(anchorPointInPoints_, CGPointZero) ){
        x += c*-anchorPointInPoints_.x+ -s*-anchorPointInPoints_.y;
        y += s*-anchorPointInPoints_.x+ c*-anchorPointInPoints_.y;
    }
    
    // Rot, Translate Matrix
    transform_ = CGAffineTransformMake( c,  s,
                                       -s,c,
                                       x,y );
    
    return transform_;
}

Remember when I mentioned that you needed something from PhysicsSprite? Well, this is it. All this does is to make sure that our Box2D shape and our sprite are in the same position when moving. It's prepared for us by Cocos2D and that makes it all the more awesome.

After copying the above code, you can now delete both PhysicsSprite.h and PhysicsSprite.mm from the project as you have completely eliminated their usefulness.

Compile and run, and everything should be fine. You are done with PolygonSprite for now.

Planning our Fruits

Before creating classes for our fruits, you need to be clear on the rules that the images and their shapes have to follow. Since you will be mapping our textures to single Box2D polygons, you must also adhere to Box2D's polygon limitations. You need to keep two things in mind:

  • Polygons must be convex, meaning that no interior angle is greater than 180.
  • Polygons must not exceed 8 vertices.

You can actually work around this limitation if you allow each body to contain multiple shapes. Box2D can handle concave shapes if you use a triangulation method and make the concave shapes out of several triangles, but this is beyond the scope of the tutorial.

To keep things simple, in this tutorial you will have a 1 Body is to 1 Shape rule.

Note: PhysicsEditor, the tool we will be covering later in this tutorial, actually contains code to automatically triangulate polygons you draw into a set of convex shapes. However, like I said we're trying to keep things simple here so will make sure to draw our shapes convex so we only have one shape per body.

Take a look at these two fruits:

Concave vs Convex

Using the Banana is not a good idea because it is naturally concave. The Watermelon on the other hand is very good because you can define a convex polygon that closely resembles its shape.

If you were to define polygon shapes that follow Box2D's rules for the two fruits, you would more or less end up with these:

Box2D Shape Outline

The Watermelon polygon fits the image perfectly, while the Banana polygon has a big empty space where the image curves inward. Box2D will treat that space as part of our object, and it might make the Banana feel unnatural when it collides with other objects, or when it gets cut.

This doesn't mean that you can't use the Banana, but rather that it is just not recommended to use the Banana. In fact, the game that you will be creating in this tutorial will use this same Banana.

Creating the First Fruit

It's time to create the first fruit: the Watermelon (a slice of it at least).

Thinking back to the flow of our PolygonSprite's initialization process, you know that initWithTexture expects a Box2D body, but the step before, initWithFile, does not provide this.

The reason for this is that you need to be creating and defining the body individually per fruit, so it will have to be the very first step, initWithWorld, that creates the body and sets any other values specific to each fruit.

To create our Box2D body, you must first know the vertices of the polygon shape you want to create. There are different ways to do this, but for this tutorial, you will be using a nifty tool called PhysicsEditor. This tool is filled with features, but you will only be using it to guide us in getting the coordinates of the vertices of our polygon.

If you don't have it, download PhysicsEditor, install it, and fire it up. You will get a blank project with 3 panels/columns.

Working with PhysicsEditor is pretty straightforward. On the left, you put all the images you want to work with. On the middle, you visually define a polygon for your image. On the right, you have the parameters for the body.

Physics Editor!

Grab watermelon.png from the Images folder of the resource kit and drag it to the left panel. You should now see the Watermelon on the center panel.

Increase the magnification, found at the bottom of this panel, to a comfortable level, and then tap on the Pentagon Button on the upper part of this panel to create a 3-sided polygon shape.

Right-Click on the polygon and choose "Add Vertex" until you have 5-8 vertices in all. Move the vertices around the edges of the Watermelon, while making sure of two things:

  • The Polygon you are creating is Convex.
  • All the pixels of the Watermelon are inside the Polygon.

Note: Another shortcut to draw the shapes is to use PhysicsEditor's magic wand tool. Just set the tolerance high (5-8) so that you end up with about 5-8 points, and tweak the points from there.

Add in all the other fruits and the bomb image from the Images folder of the resource kit and do the same thing for them.

You should be defining shapes for the following images:

  • banana.png
  • bomb.png
  • grapes.png
  • pineapple.png
  • strawberry.png
  • watermelon.png

When you're finished, on the upper right corner, change the value of Exporter to "Box2D generic (PLIST)", and you should end up with something like this:

Define Shapes Easily with PhysicsEditor!

Hit "Publish", or "Publish As", to export the PLIST file containing the vertex information. Save the file as fruits.plist.

As an example, the fruits.plist you used for this tutorial is inside the Misc folder of the resource kit.

You only want to look at the information contained in the PLIST file, so do not add this file to your project, but rather, just open fruits.plist using Xcode to view its contents in an organized manner.

Click on the triangle icon beside "bodies" to expand this section and you will see the list of images that you defined shapes for. You need to drill down to the deepest level to get the Watermelon's polygon vertices like so:

The PhysicsEditor PLIST

Expand watermelon/fixtures/Item 0/polygons and you should now see another Item 0 of Type Array under polygons. This last array is your shape. If you had properly defined a convex shape with 8 or less vertices, you should only see one array under polygons.

If you see more than one, i.e Item 0 Array, Item 1 Array, etc, then it means PhysicsEditor made a complex shape because you either defined too many vertices, or you formed a concave polygon. If this happens, go back to PhysicsEditor and fix your shape.

Next, expand the Item 0 of type Array to see the final list of items. These are your vertices, and the value you see at the right side with this format { number, number } are your x & y coordinates for each vertex.

Now that you have the exact values for your polygon's vertices, you can proceed with creating the Watermelon class.

In Xcode, create a new file with the iOS\cocos2d v2.x\CCNode Class template. Make it a subclass of PolygonSprite and name it Watermelon. Open Watermelon.h and make the following changes:

// Add to top of file
#import "PolygonSprite.h"

Switch to Watermelon.m, rename it to Watermelon.mm, and add the following init method:

// Add inside the @implementation
-(id)initWithWorld:(b2World *)world
{
    int32 count = 7;
    NSString *file = @"watermelon.png";
    b2Vec2 vertices[] = {
        b2Vec2(5.0/PTM_RATIO,15.0/PTM_RATIO),
        b2Vec2(18.0/PTM_RATIO,7.0/PTM_RATIO),
        b2Vec2(32.0/PTM_RATIO,5.0/PTM_RATIO),
        b2Vec2(48.0/PTM_RATIO,7.0/PTM_RATIO),
        b2Vec2(60.0/PTM_RATIO,14.0/PTM_RATIO),
        b2Vec2(34.0/PTM_RATIO,59.0/PTM_RATIO),
        b2Vec2(28.0/PTM_RATIO,59.0/PTM_RATIO)
    };
    CGSize screen = [[CCDirector sharedDirector] winSize];
    
    b2Body *body = [self createBodyForWorld:world position:b2Vec2(screen.width/2/PTM_RATIO,screen.height/2/PTM_RATIO) rotation:0 vertices:vertices vertexCount:count density:5.0 friction:0.2 restitution:0.2];
    
    if ((self = [super initWithFile:file body:body original:YES]))
    {
        // We will initialize more values for the fruit here later
    }
    return self;
}

In the above code, you first define how many vertices there are, which in this case is 7. Next, you create an array of vertices containing all the coordinates you just saw in the PLIST. You use this information to create a body using the convenience method you defined in PolygonSprite.

You put in a little friction so that the shapes don't slide endlessly, and you also put in a little restitution so that shapes don't stop when they bounce against each other.

Last, you create the object by calling the superclass' initialization and pass in the name of the image file, the Box2D body, and state that this is an original fruit.

You need the watermelon images from the resource kit, so now would be a good time to just add all the graphic resources you need for the rest of the tutorial.

In your Project Navigator panel, right-click on Resources and select "Add Files to CutCutCut". Add the Images folder from the resource kit to the project. Make sure that "Copy items into destination group's folder" is checked and "Create groups for any added folders" is selected.

Follow the same steps to create Banana, Grapes, Pineapple, Strawberry, and Bomb.

You only tackled how to create the first fruit step by step since it is basically a rinse & repeat process for each fruit. The resource kit contains ready-made fruit and bomb classes in the Classes folder for you to look at in case you still need guidance, or you can add them all to your project if you want to skip this step.

Compile and run, and make sure everything is fine.

Adding a Fruit to the Scene

So far, nothing has been happening on screen yet, and you are obviously itching to see the fruits of your labor - pun intended! :]

Switch to HelloWorldLayer.h and make the following changes:

// Add to top of file
#import "PolygonSprite.h"

// Add inside the @interface
CCArray *_cache;

// Add after the @interface
@property(nonatomic,retain)CCArray *cache;

Switch back to HelloWorldLayer.mm and make these changes:

// Add to top of file
#import "Watermelon.h"

// Add inside the @implementation
@synthesize cache = _cache;

// Add inside the init method, below [self initPhysics]
[self initSprites];

// Add inside the dealloc method, before calling [super dealloc]
[_cache release];
_cache = nil;

// Add anywhere inside the @implementation and before the @end

-(void)initSprites
{
    _cache = [[CCArray alloc] initWithCapacity:53];
    
    // Just create one sprite for now. This whole method will be replaced later.
    PolygonSprite *sprite = [[Watermelon alloc] initWithWorld:world];
    [self addChild:sprite z:1];
    [sprite activateCollisions];
    [_cache addObject:sprite];
}

You declare a cache array, which will hold all the fruits and bombs you create in the future. Next, you create 1 Watermelon and add it to the scene. You call activateCollisions so that the Watermelon does not pass through the walls.

Compile and run, and you should see a Watermelon falling from the center area of the screen, and land at the bottom as shown below.

The Fruit of your Labor

You may notice that the Watermelon isn't exactly at the center. The reason for this is that you positioned the object based on its Box2D body, and our Box2D body's origin is at the lower-left corner of the object. The thin outline around the Watermelon is visible because debug drawing mode is still turned on.

Where To Go From Here?

Here is a sample project with all of the code from the above tutorial.

That's it for part 1 of the series. So far, you have a Watermelon Textured Polygon that just falls to the bottom of the screen.

But instead of drawing a rectangular sprite with transparent space like you usually see in Box2D tutorials, this uses PRKit to only draw the parts of the texture that correspond to the vertices of the Box2D body. This will come in handy soon!

Now you're ready for Part 2 of the tutorial, where you add the ability to slice the fruits!

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


This is a post by iOS Tutorial Team Member Allen Tan, an iOS developer and co-founder at White Widget. You can also find him on and Twitter.

Allen Tan

Contributors

Allen Tan

Author

Over 300 content creators. Join our team.