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

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. Welcome to the third part of a tutorial series that shows you how to make a sprite cutting game similar to the game Fruit Ninja by Halfbrick […] By Allen Tan.

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

Adding a Scoring System

The game won't be much of a game without an objective and an end, so you need to put some sort of scoring system in place.

You will count score by the number of slices the player makes. You also give the player 3 lives, or chances if you will, which decrease whenever an uncut fruit goes out of the screen bounds. The game ends when the player loses 3 fruits.

Switch to HelloWorldLayer.h and make this change:

// Add inside @interface
int _cuts;
int _lives;
CCLabelTTF *_cutsLabel;

Switch back to HelloWorldLayer.mm and make these changes:

// Add inside the init method, right after [self initSprites]
[self initHUD];

// Add these methods
-(void)initHUD
{
    CGSize screen = [[CCDirector sharedDirector] winSize];
    
    _cuts = 0;
    _lives = 3;
    
    for (int i = 0; i < 3; i++)
    {
        CCSprite *cross = [CCSprite spriteWithFile:@"x_unfilled.png"];
        cross.position = ccp(screen.width - cross.contentSize.width/2 - i*cross.contentSize.width, screen.height - cross.contentSize.height/2);
        [self addChild:cross z:4];
    }
    
    CCSprite *cutsIcon = [CCSprite spriteWithFile:@"fruit_cut.png"];
    cutsIcon.position = ccp(cutsIcon.contentSize.width/2, screen.height - cutsIcon.contentSize.height/2);
    [self addChild:cutsIcon];
    
    _cutsLabel = [CCLabelTTF labelWithString:@"0" fontName:@"Helvetica Neue" fontSize:30];
    _cutsLabel.anchorPoint = ccp(0, 0.5);
    _cutsLabel.position = ccp(cutsIcon.position.x + cutsIcon.contentSize.width/2 +                _cutsLabel.contentSize.width/2,cutsIcon.position.y);
    [self addChild:_cutsLabel z:4];
}

-(void)restart
{
    [[CCDirector sharedDirector] replaceScene:[HelloWorldLayer scene]];
}

-(void)endGame
{
    [self unscheduleUpdate];
    CCMenuItemLabel *label = [CCMenuItemLabel itemWithLabel:[CCLabelTTF labelWithString:@"RESTART"fontName:@"Helvetica Neue"fontSize:50] target:self selector:@selector(restart)];
    CCMenu *menu = [CCMenu menuWithItems:label, nil];
    CGSize screen = [[CCDirector sharedDirector] winSize];
    menu.position = ccp(screen.width/2, screen.height/2);
    [self addChild:menu z:4];
}

-(void)subtractLife
{
    CGSize screen = [[CCDirector sharedDirector] winSize];
    _lives--;
    CCSprite *lostLife = [CCSprite spriteWithFile:@"x_filled.png"];
    lostLife.position = ccp(screen.width - lostLife.contentSize.width/2 - _lives*lostLife.contentSize.width, screen.height - lostLife.contentSize.height/2);
    [self addChild:lostLife z:4];
    
    if (_lives <= 0)
    {
        [self endGame];
    }
}

In the interface, you set up variables to count the cuts and the lives. You also declare a label that shows the player's current score.

The initHUD method creates 3 marker images on the upper-right corner of the screen to represent the player's lives. It also puts an image representing the score, and the score value itself on the upper-left corner.

The subtractLife method replaces each marker image with a new marker image, representing a life lost, whenever it is called. It also checks if the player still has enough lives, if not, then the game should end.

The endGame method simply unschedules the game logic and creates a restart button on the screen. If this button is pressed, then the game is restarted.

The restart method just reloads the scene, going back to the very beginning of the game.

Now that you've built all these methods and variables, it's time to add them to the game logic.

Still in HelloWorldLayer.mm, make the following changes:

// Add to the splitPolygonSprite method, inside the if (sprite1VerticesAcceptable && sprite2VerticesAcceptable) statement
_cuts++;
[_cutsLabel setString:[NSString stringWithFormat:@"%d",_cuts]];

// Add to the cleanSprites method, inside the if (spritePosition.y < -64 && yVelocity < 0) statement
if (sprite.type != kTypeBomb)
{
    [self subtractLife];
}

Whenever a polygon is successfully split, the score is incremented, and the label that shows the score is updated. If any original sprites fall to the bottom, then you subtract a life from the player.

Compile and run. The game is almost complete!

Game Over!

Making The Game More Challenging

To make things more interesting, you are going to add bombs to the game. You've already initialized 3 Bomb objects earlier, but you haven't used them in any of the game mechanics.

Bombs are independent, so they should be tossed anytime regardless of the toss type. If a player accidentally slices a bomb, it will explode and take 1 life from the player.

Make these changes to HelloWorldLayer.mm:

// Add to the spriteLoop method, inside if (curTime > _nextTossTime), right after PolygonSprite *sprite;
int chance = arc4random()%8;
if (chance == 0)
{
    CCARRAY_FOREACH(_cache, sprite)
    {
        if (sprite.state == kStateIdle && sprite.type == kTypeBomb)
        {
            [self tossSprite:sprite];
            break;
        }
    }
}

// Add to the splitPolygonSprite method, inside the if (sprite.original) statement
if (sprite.type == kTypeBomb)
{
    [self subtractLife];
}
else
{
//placeholder
}

The code is pretty straightforward and similar to what you did before. The first part adds the tossing mechanism for the Bombs in a similar fashion to how the fruits were tossed, but this time without any checks for the toss type, and without counting how many bombs have been tossed.

Bombs are also tossed only by a random chance. By calling the modulo 8 operation on a random number, and requiring the result to be 0, there is only a 1/8 chance that a bomb will get tossed at every interval.

The second part adds a check to the sprite that was split in the splitPolygonSprite method. If the sprite happens to be a bomb, then the player loses a life by calling the subtractLife method you made earlier.

Compile and run. Bombs away!

Bombs Away!

More Life With Particle Effects

With the game mechanic complete, you can focus on polishing the game. You certainly need to add life to the game. For starters, cutting fruits look dull, bombs don't explode, and the background just seems to be too static.

You can improve the scene using particle systems. Particle Systems allow you to efficiently create a large number of small objects using the same sprite. Cocos2D already comes with a customizable particle system, and it works great in conjunction with Particle Designer for visually setting them up.

Making particle systems in Particle Designer is easy; so easy in fact that you won't be covering how to do that in this tutorial. Instead, I've already created some particle systems that you can use. Particle Designer exports particle systems in PLIST format. All you need to do is load the PLIST files into Cocos2D.

Grab the resources for this tutorial if you haven't already, and in your Project Navigator panel, right-click on Resources and select "Add Files to CutCutCut". Add the Particles folder from the resources to the project. While you're at it, also add the Sounds folder to the project. Make sure that "Copy items into destination group's folder" is checked and "Create groups for any added folders" is selected.

These are the particle systems that should have been added to your project:

  • banana_splurt.plist
  • blade_sparkle.plist
  • explosion.plist
  • grapes_splurt.plist
  • pineapple_splurt.plist
  • strawberry_splurt.plist
  • sun_pollen.plist
  • watermelon_splurt.plist

You have 5 particles that spray blobs, which you will call "splurt", for when the various fruits are cut. An explosion particle for when the player slices a bomb. A sparkle effect that follows the blade, and a floating pollen effect for the background.

Switch to HelloWorldLayer.h and add the following inside the @interface:

CCParticleSystemQuad *_bladeSparkle;

Next, switch to HelloWorldLayer.mm again, and make the following changes:

// Add inside the init method
_bladeSparkle = [CCParticleSystemQuad particleWithFile:@"blade_sparkle.plist"];
[_bladeSparkle stopSystem];
[self addChild:_bladeSparkle z:3];

// Add inside the initBackground method
CCParticleSystemQuad *sunPollen = [CCParticleSystemQuad particleWithFile:@"sun_pollen.plist"];
[self addChild:sunPollen];

//Add inside ccTouchesBegan
_bladeSparkle.position = location;
[_bladeSparkle resetSystem];

//Add inside ccTouchesMoved
_bladeSparkle.position = location;

// Add inside ccTouchesEnded
[_bladeSparkle stopSystem];

You added the floating pollen particles to the background, and made the sparkle effect follow the user's touch.

Calling stopSystem simply stops the particle system from emitting sprites, and calling resetSystem makes them emit the sprites again. Both of these particles are endless, meaning that they won't stop emitting unless you call stopSystem.

For the splurt and explosion effects, make the following changes to PolygonSprite.h

// Add inside the @interface
CCParticleSystemQuad *_splurt;

// Add after the @interface
@property(nonatomic,assign)CCParticleSystemQuad *splurt;

Switch to PolygonSprite.mm, and add the following inside the @implementation:

@synthesize splurt = _splurt;

Next, make the following changes to the subclasses of PolygonSprite (the fruits and the bomb):

// Add inside Banana.mm init right after setting the type
self.splurt = [CCParticleSystemQuad particleWithFile:@"banana_splurt.plist"];
[self.splurt stopSystem];

// Add inside Bomb.mm init right after setting the type
self.splurt = [CCParticleSystemQuad particleWithFile:@"explosion.plist"];
[self.splurt stopSystem];

// Add inside Grapes.mm init right after setting the type
self.splurt = [CCParticleSystemQuad particleWithFile:@"grapes_splurt.plist"];
[self.splurt stopSystem];

// Add inside Pineapple.mm init right after setting the type
self.splurt = [CCParticleSystemQuad particleWithFile:@"pineapple_splurt.plist"];
[self.splurt stopSystem];

// Add inside Strawberry.mm init right after setting the type
self.splurt = [CCParticleSystemQuad particleWithFile:@"strawberry_splurt.plist"];
[self.splurt stopSystem];

// Add inside Watermelon.mm init right after setting the type
self.splurt = [CCParticleSystemQuad particleWithFile:@"watermelon_splurt.plist"];
[self.splurt stopSystem];

You just added a particle system to the structure of PolygonSprites, and assigned a particle system for each type.

Switch back to HelloWorldLayer.mm and make these changes:

// Add this line per fruit and bomb in the initSprites method
[self addChild:sprite.splurt z:3];

// Add inside the splitPolygonSprite method, inside the if (sprite.original) statement
b2Vec2 convertedWorldEntry = b2Vec2(worldEntry.x*PTM_RATIO,worldEntry.y*PTM_RATIO);
b2Vec2 convertedWorldExit = b2Vec2(worldExit.x*PTM_RATIO,worldExit.y*PTM_RATIO);
float midX = midpoint(convertedWorldEntry.x, convertedWorldExit.x);
float midY = midpoint(convertedWorldEntry.y, convertedWorldExit.y);
sprite.splurt.position = ccp(midX,midY);
[sprite.splurt resetSystem];

You add all the particle systems to our game layer inside the initSprite method, and you make the particle effect appear in the middle of the cutting line when an original fruit or bomb is split.

Compile and run, and see particles fly when you slice objects!

Particles Everywhere

Allen Tan

Contributors

Allen Tan

Author

Over 300 content creators. Join our team.