How to Make a Fruit Ninja Game in Phaser – Part 2

In Part 1 of the Fruit Ninja tutorial we started creating a Fruit Ninja game. In our game we already have fruits and bombs, which we can cut. If you cut a fruit, you increase your score, which is shown in the screen. Otherwise, if you cut a bomb, you lose. In this tutorial we will the following content to our game, making it more fun to play:

  • Player lives, so the player has a number of bombs he can cut before losing
  • A game over screen, which will show the current score and the highest score so far (the highest score will be saved)
  • Particle effects when the player cut something, making it visibly more attractive
  • A special fruit, that stops when you cut it for the first time, allowing you to make more cuts in a row

To read this tutorial, it is important that you are familiar with the following concepts:

  • Javascript and object-oriented concepts.
  • Basic Phaser concepts, such as: states, sprites, groups and arcade physics

Learn Phaser by building 15 games

If you want to master Phaser and learn how to publish Phaser games as native games for iOS and Android feel free to check Zenva‘s online course The Complete Mobile Game Development Course – Build 15 Games.

Assets copyright

The bomb asset used in this tutorial was made by Alucard under Creative Commons License (http://opengameart.org/content/bomb-2d), which allows commercial use with attribution.

Source code files

You can download the tutorial source code files here.

Game states

We will use the following states to run our game:

  • Boot State: loads a json file with the level information and starts the Loading State
  • Loading Sate: loads all the game assets, and starts the Level State
  • Level State: creates the game groups and prefabs

The code for BootState and Loading is exactly the same from the last tutorial, so I will omit them.

The LevelState however, has some changes that were necessary to allow the new content. So, I will show those changes when they are necessary.

Player lives

To make the game work with player lives, we will first create a Lives prefab as shown below. The Lives prefab will have the lives asset texture, but it will be invisible. This way, we can use it to create new sprites with the same texture. This is done in the constructor, where we iterate through each life and create a new Phaser sprite. The position of this new sprite is given by the Lives prefab position plus a spacing, while the texture is the same as Lives prefab. All those sprites are stored in an array, so we can manipulate them later in the “die” method.

var FruitNinja = FruitNinja || {};

FruitNinja.Lives = function (game_state, name, position, properties) {
    "use strict";
    var live_index, life;
    FruitNinja.Prefab.call(this, game_state, name, position, properties);
    
    this.visible = false;
    
    this.lives = properties.lives;
    this.lives_sprites = [];
    // create a sprite for each life
    for (live_index = 0; live_index < this.lives; live_index += 1) {
        life = new Phaser.Sprite(this.game_state.game, position.x + (live_index * properties.lives_spacing), position.y, this.texture);
        this.lives_sprites.push(life);
        this.game_state.groups.hud.add(life);
    }
};

FruitNinja.Lives.prototype = Object.create(FruitNinja.Prefab.prototype);
FruitNinja.Lives.prototype.constructor = FruitNinja.Lives;

FruitNinja.Lives.prototype.die = function () {
    "use strict";
    var life;
    this.lives -= 1;
    // kill the last life
    life = this.lives_sprites.pop();
    life.kill();
    // if there are no more lives, it's game over
    if (this.lives === 0) {
        this.game_state.game_over();
    }
};

The “die” method will be called when the player cuts a bomb. It will decrease the number of lives, kill the last life in the lives array and check the player remaining number of lives. If there are no more lives, it’s game over.

Finally, we have to change the “cut” method in the Bomb prefab to call this new “die” method (remember that it was calling “game_over” before).

FruitNinja.Bomb.prototype.cut = function () {
    "use strict";
    FruitNinja.Cuttable.prototype.cut.call(this);
    // if a bomb is cut, the player lose a life
    this.game_state.prefabs.lives.die();
    this.kill();
};

You can already try playing with the player lives and see if it’s working.

lives

Game over screen

Now that we already have player lives, it would be interesting to show a game over screen when the player actually loses, instead of just restarting the game. To make it simple, our game over screen will be only a panel which will be shown over the game, with a game over message, the current score and the highest score so far. We will create a GameOverPanel prefab for that, as shown below. First, we will lower the alpha of our panel, so the player can see the game behind it. Next, we will start a tween animation to show it. The idea is that the panel will come from the bottom of the screen and, when it arrives the top, it will show the game over message, calling the “show_game_over” method.

var FruitNinja = FruitNinja || {};

FruitNinja.GameOverPanel = function (game_state, name, position, properties) {
    "use strict";
    var movement_animation;
    FruitNinja.Prefab.call(this, game_state, name, position, properties);
    
    this.text_style = properties.text_style;
    
    this.alpha = 0.5;
    // create a tween animation to show the game over panel
    movement_animation = this.game_state.game.add.tween(this);
    movement_animation.to({y: 0}, properties.animation_time);
    movement_animation.onComplete.add(this.show_game_over, this);
    movement_animation.start();
};

FruitNinja.GameOverPanel.prototype = Object.create(FruitNinja.Prefab.prototype);
FruitNinja.GameOverPanel.prototype.constructor = FruitNinja.GameOverPanel;

FruitNinja.GameOverPanel.prototype.show_game_over = function () {
    "use strict";
    var game_over_text, current_score_text, highest_score_text;
    // add game over text
    game_over_text = this.game_state.game.add.text(this.game_state.game.world.width / 2, this.game_state.game.world.height * 0.4, "Game Over", this.text_style.game_over);
    game_over_text.anchor.setTo(0.5);
    this.game_state.groups.hud.add(game_over_text);
    
    // add current score text
    current_score_text = this.game_state.game.add.text(this.game_state.game.world.width / 2, this.game_state.game.world.height * 0.5, "Score: " + this.game_state.score, this.text_style.current_score);
    current_score_text.anchor.setTo(0.5);
    this.game_state.groups.hud.add(current_score_text);
    
    // add highest score text
    highest_score_text = this.game_state.game.add.text(this.game_state.game.world.width / 2, this.game_state.game.world.height * 0.6, "Highest score: " + localStorage.highest_score, this.text_style.highest_score);
    highest_score_text.anchor.setTo(0.5);
    this.game_state.groups.hud.add(highest_score_text);
    
    // add event to restart level
    this.inputEnabled = true;
    this.events.onInputDown.add(this.game_state.restart_level, this.game_state);
};

The “show_game_over” method will add three Phaser texts on the screen for the following information: 1) game over message; 2) current score; 3) highest score. Notice that each text has its own style, which were all passed in the constructor through the properties parameter. Also, the position is calculated using the game world width and height, so it should work with different screen sizes. Finally, “show_game_over” will add an input event to restart the game when the player touches the screen.

We still have to create this GameOverPanel in the LevelState, and we will do that in the “game_over_method”. First, we will update the highest score. To save data from your game, you can use the browser localStorage. Any data saved this way will be kept even when the browser is reloaded (for more information: http://www.w3schools.com/html/html5_webstorage.asp). So, first we will check if our score is higher than “localStorage.highest_score” (or if there is no highest score yet) and if so, update it.

FruitNinja.LevelState.prototype.game_over = function () {
    "use strict";
    var game_over_panel, game_over_position, game_over_bitmap, panel_text_style;
    // if current score is higher than highest score, update it
    if (!localStorage.highest_score || this.score > localStorage.highest_score) {
        localStorage.highest_score = this.score;
    }
    
    // create a bitmap do show the game over panel
    game_over_position = new Phaser.Point(0, this.game.world.height);
    game_over_bitmap = this.add.bitmapData(this.game.world.width, this.game.world.height);
    game_over_bitmap.ctx.fillStyle = "#000";
    game_over_bitmap.ctx.fillRect(0, 0, this.game.world.width, this.game.world.height);
    panel_text_style = {game_over: {font: "32px Arial", fill: "#FFF"},
                       current_score: {font: "20px Arial", fill: "#FFF"},
                       highest_score: {font: "18px Arial", fill: "#FFF"}};
    // create the game over panel
    game_over_panel = new FruitNinja.GameOverPanel(this, "game_over_panel", game_over_position, {texture: game_over_bitmap, group: "hud", text_style: panel_text_style, animation_time: 500});
    this.groups.hud.add(game_over_panel);
};

Next, we have to create GameOverPanel. The texture of this sprite will be a Phaser bitmap, which is a Phaser object with an HTML Canvas, so you can use it like a canvas (for more information, check the documentation). Our bitmap will be from the size of the world, and we change its color as we would do with a HTML Canvas. Finally, we set the text style for each one of the GameOverPanel texts, and create it.

You can already try playing with the game over screen to see if it works. Try scoring higher than before and see if highest score is properly updated, even if you reload the browser.

game_over

Adding a particle effect

The next thing we are going to add to our game is purely visual, but you will see it adds a lot to the game. Currently, when we cut a fruit or a bomb, it simply disappears, which is kinda weird from the player’s point of view. Now we are going to add a particle effect, which will work like a visual feedback to the player, so he automatically understands that he actually cut something.

This will be done changing the “cut” method in Cuttable. We start creating a Phaser emitter in the prefab position and setting the particles asset (you will notice I added a new asset called “particle_image” in the level JSON file). Next we have to set the particles minimum and maximum velocity in both directions, as well as the gravity acting on them. Finally, we start the emitter, telling it how long it should emit and how many particles (for more information about the Emitter class, check Phaser documentation).

FruitNinja.Cuttable.prototype.cut = function () {
    "use strict";
    var emitter;
    // create emitter in prefab position
    emitter = this.game_state.game.add.emitter(this.x, this.y);
    emitter.makeParticles("particle_image");
    // set particles speed
    emitter.minParticleSpeed.setTo(-200, -200);
    emitter.maxParticleSpeed.setTo(200, 200);
    emitter.gravity = 0;
    // start emitter
    emitter.start(true, 700, null, 1000);
};

All the values I used as parameters for the emitter methods (such as velocity, number of particles) were decided experimentally by trial and error. This is usually done when we have to decide parameters in a game, so you should try some values and see which ones fits your game best.

By now, you can already try cutting some fruits and bombs and see the particles visual effect.

Special fruit

The last thing we will add is a special fruit. When the player cut this fruit, it will stop for some time, so the player can keep cutting it and increase the score. To do that, we will create a SpecialFruit prefab as shown below. In the constructor we will create a kill timer, which won’t be started until the fruit is cut. Then, in the “cut” method, we increase the player score and check if the kill timer is not running. If that’s the case, this was the first cut, so we should stop the fruit (which is done by setting body.allowGravity to false and making the velocity in both directions 0) and start the kill timer. The next time the fruit is cut, the kill timer will already be running, so it will only increase the player score.

var FruitNinja = FruitNinja || {};

FruitNinja.SpecialFruit = function (game_state, name, position, properties) {
    "use strict";
    var frame_index;
    FruitNinja.Cuttable.call(this, game_state, name, position, properties);
    
    this.body.setSize(20, 20);
    
    // create a timer with autoDestroy = false, so it won't be killed
    this.kill_timer = this.game_state.game.time.create(false);
};

FruitNinja.SpecialFruit.prototype = Object.create(FruitNinja.Cuttable.prototype);
FruitNinja.SpecialFruit.prototype.constructor = FruitNinja.SpecialFruit;

FruitNinja.SpecialFruit.prototype.kill = function () {
    "use strict";
    Phaser.Sprite.prototype.kill.call(this);
    // prepare the fruit so it can be reused
    this.body.allowGravity = true;
    this.kill_timer.stop();
};

FruitNinja.SpecialFruit.prototype.cut = function () {
    "use strict";
    FruitNinja.Cuttable.prototype.cut.call(this);
    // if a fruit is cut, increment score
    this.game_state.score += 1;
    // if it's the first cut, stops the fruit and start the timer to kill it
    if (!this.kill_timer.running) {
        this.body.allowGravity = false;
        this.body.velocity.y = 0;
        this.body.velocity.x = 0;
        this.kill_timer.add(Phaser.Timer.SECOND * 3, this.kill, this);
        this.kill_timer.start();
    }
};

There is important things to notice here, though. When the fruit is killed, we expect the object to be reused for the next fruit, because we’re keeping it in a pool of objects and we don’t want to create a new object. To allow this, we have to set body.allowGravity back to true and stop the timer in the “kill” method, so it will still be working when it is reused. Also, when we created the timer in the constructor, we set the parameter autoDestroy false, otherwise the timer would be destroyed when there were no more events.

Now that we have our special fruit prefab, we have to create a spawner for it. For that, we will create a SpecialFruitSpawner which will overwrite the “create_object” method to return a SpecialFruit. For the asset I just used a different fruit from our fruits spritesheet. Notice how it was easy to add a new kind of fruit since we have our Cuttable and Spawner generic prefabs. If we wanted to add another kind of fruit (or bomb), we could follow the same process, overwritting only the methods that are different from the original Cuttable and Spawner classes. This makes it much easier to add content to our game.

var FruitNinja = FruitNinja || {};

FruitNinja.SpecialFruitSpawner = function (game_state, name, position, properties) {
    "use strict";
    FruitNinja.Spawner.call(this, game_state, name, position, properties);
    
    this.frames = properties.frames;
};

FruitNinja.SpecialFruitSpawner.prototype = Object.create(FruitNinja.Spawner.prototype);
FruitNinja.SpecialFruitSpawner.prototype.constructor = FruitNinja.SpecialFruitSpawner;

FruitNinja.SpecialFruitSpawner.prototype.create_object = function (name, position, velocity) {
    "use strict";
    // return new fruit with random frame
    return new FruitNinja.SpecialFruit(this.game_state, name, position, {texture: "fruits_spritesheet", group: "special_fruits", frame: 15, velocity: velocity});
};

Finally, our game is done and you can try cutting some special fruits to beat your highest score.

special_fruit

Finishing the game

With that, we finish our game. In the following tutorial we will add a title screen and a new game mode!