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

Fruit Ninja is a game where you have to cut fruits by swiping your cellphone screen while avoiding cutting bombs. In this tutorial, we will build a Fruit Ninja game using Phaser. 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

The following content will be covered in this tutorial:

  • Reading the whole level (assets, groups and prefabs) from a JSON file
  • Detect swipes in the screen and checking for collisions with game objects
  • Differently handle swipes according to the cut object

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.

Source code files

You can download the tutorial source code files here.

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.

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 LoadingState are shown below. BootState will load and parse the JSON file and starts LoadingState. Next, LoadingState load all the game assets, described in the JSON file. When all assets are loaded, LoadingState starts LevelState.

var FruitNinja = FruitNinja || {};

FruitNinja.BootState = function () {
    "use strict";
    Phaser.State.call(this);
};

FruitNinja.prototype = Object.create(Phaser.State.prototype);
FruitNinja.prototype.constructor = FruitNinja.BootState;

FruitNinja.BootState.prototype.init = function (level_file) {
    "use strict";
    this.level_file = level_file;
};

FruitNinja.BootState.prototype.preload = function () {
    "use strict";
    this.load.text("level1", this.level_file);
};

FruitNinja.BootState.prototype.create = function () {
    "use strict";
    var level_text, level_data;
    level_text = this.game.cache.getText("level1");
    level_data = JSON.parse(level_text);
    this.game.state.start("LoadingState", true, false, level_data);
};
var FruitNinja = FruitNinja || {};

FruitNinja.LoadingState = function () {
    "use strict";
    Phaser.State.call(this);
};

FruitNinja.prototype = Object.create(Phaser.State.prototype);
FruitNinja.prototype.constructor = FruitNinja.LoadingState;

FruitNinja.LoadingState.prototype.init = function (level_data) {
    "use strict";
    this.level_data = level_data;
};

FruitNinja.LoadingState.prototype.preload = function () {
    "use strict";
    var assets, asset_loader, asset_key, asset;
    assets = this.level_data.assets;
    for (asset_key in assets) { // load assets according to asset key
        if (assets.hasOwnProperty(asset_key)) {
            asset = assets[asset_key];
            switch (asset.type) {
            case "image":
                this.load.image(asset_key, asset.source);
                break;
            case "spritesheet":
                this.load.spritesheet(asset_key, asset.source, asset.frame_width, asset.frame_height, asset.frames, asset.margin, asset.spacing);
                break;
            }
        }
    }
};

FruitNinja.LoadingState.prototype.create = function () {
    "use strict";
    this.game.state.start("GameState", true, false, this.level_data);
};

LevelState has a quite more things to do, as presented below. As you can see, our JSON file contains the information about assets, groups and prefabs. The assets were already loaded in the LoadingState, so now in the LevelState we only have to create the groups and instantiate the prefabs.

{
    "assets": {
        "fruit_image": {"type": "image", "source": "assets/images/fruit.png"},
        "bomb_image": {"type": "image", "source": "assets/images/bomb.png"},
        "background_image": {"type": "image", "source": "assets/images/background.png"},
        "fruits_spritesheet": {"type": "spritesheet", "source": "assets/images/fruits.png", "frame_width": 28, "frame_height": 28}
    },
    "groups": [
        "background",
        "spawners",
        "fruits",
        "bombs",
        "cuts",
        "hud"
    ],
    "prefabs": {
        "background": {
            "type": "background",
            "position": {"x": 0, "y": 0},
            "properties": {
                "texture": "background_image",
                "group": "background"
            }
        },
        "fruit_spawner": {
            "type": "fruit_spawner",
            "position": {"x": 0, "y": 0},
            "properties": {
                "texture": "",
                "group": "spawners",
                "pool": "fruits",
                "spawn_time": {"min": 1, "max": 3},
                "velocity_x": {"min": -100, "max": 100},
                "velocity_y": {"min": 850, "max": 1000},
                "frames": [20, 21, 23, 35, 38]
            }
        },
        "bomb_spawner": {
            "type": "bomb_spawner",
            "position": {"x": 0, "y": 0},
            "properties": {
                "texture": "",
                "group": "spawners",
                "pool": "bombs",
                "spawn_time": {"min": 1, "max": 3},
                "velocity_x": {"min": -100, "max": 100},
                "velocity_y": {"min": 850, "max": 1000}
            }
        }
    }
}

LevelState code is shown below. To create level groups, we just have to go trough all groups in the JSON file. To create level prefabs we need the prefab name, its type, position, texture, group and properties. Then, we go through each one of the prefabs described in our JSON file and call the “create_prefab” method, which will instantiate the correct prefab. To do that, we’ll keep an object that maps each prefab type to its correct constructor, as you can see in LevelState constructor. This way, to instantiate a new prefab we check if the type is present in the “prefab_classes” objects, and if so, we call the correct constructor. Notice that this only works because all prefabs have the same constructor.

var FruitNinja = FruitNinja || {};

FruitNinja.LevelState = function () {
    "use strict";
    Phaser.State.call(this);
    
    this.prefab_classes = {
        "fruit_spawner": FruitNinja.FruitSpawner.prototype.constructor,
        "bomb_spawner": FruitNinja.BombSpawner.prototype.constructor,
        "background": FruitNinja.Prefab.prototype.constructor
    };
};

FruitNinja.LevelState.prototype = Object.create(Phaser.State.prototype);
FruitNinja.LevelState.prototype.constructor = FruitNinja.LevelState;

FruitNinja.LevelState.prototype.init = function (level_data) {
    "use strict";
    this.level_data = level_data;
    
    this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
    this.scale.pageAlignHorizontally = true;
    this.scale.pageAlignVertically = true;
    
    // start physics system
    this.game.physics.startSystem(Phaser.Physics.ARCADE);
    this.game.physics.arcade.gravity.y = 1000;
    
    this.MINIMUM_SWIPE_LENGTH = 50;
    
    this.score = 0;
};

FruitNinja.LevelState.prototype.create = function () {
    "use strict";
    var group_name, prefab_name;
    
    // create groups
    this.groups = {};
    this.level_data.groups.forEach(function (group_name) {
        this.groups[group_name] = this.game.add.group();
    }, this);
    
    // create prefabs
    this.prefabs = {};
    for (prefab_name in this.level_data.prefabs) {
        if (this.level_data.prefabs.hasOwnProperty(prefab_name)) {
            // create prefab
            this.create_prefab(prefab_name, this.level_data.prefabs[prefab_name]);
        }
    }
    
    // add events to check for swipe
    this.game.input.onDown.add(this.start_swipe, this);
    this.game.input.onUp.add(this.end_swipe, this);
    
    this.init_hud();
};

FruitNinja.LevelState.prototype.create_prefab = function (prefab_name, prefab_data) {
    "use strict";
    var prefab;
    // create object according to its type
    if (this.prefab_classes.hasOwnProperty(prefab_data.type)) {
        prefab = new this.prefab_classes[prefab_data.type](this, prefab_name, prefab_data.position, prefab_data.properties);
    }
};

FruitNinja.LevelState.prototype.game_over = function () {
    "use strict";
    this.game.state.restart(true, false, this.level_data);
};

Prefabs

In Phaser, prefabs are objects that extend Phaser.Sprite, allowing us to add custom properties and methods. In this tutorial, all our game objects will be implemented using prefabs, so we will write a generic Prefab, which all our prefabs will extend. Notice that our Prefab will have the Game State, in case we need to use it, and allows us to choose a group and frame.

var FruitNinja = FruitNinja || {};

FruitNinja.Prefab = function (game_state, name, position, properties) {
    "use strict";
    Phaser.Sprite.call(this, game_state.game, position.x, position.y, properties.texture);
    
    this.game_state = game_state;
    
    this.name = name;
    
    this.game_state.groups[properties.group].add(this);
    this.frame = properties.frame;
    
    this.game_state.prefabs[name] = this;
};

FruitNinja.Prefab.prototype = Object.create(Phaser.Sprite.prototype);
FruitNinja.Prefab.prototype.constructor = FruitNinja.Prefab;

Detecting swipes

We’ll use the following strategy to detect swipes:

  1. When the user touches the screen, we save the touch position as a starting point
  2. When the user releases the screen, we save the touch position as a end point
  3. If the distance between both starting and end points is greater than a minimum distance, we detect it as a swipe, and create a line between these two points

The code below shows how this works. First, we add onDown and onUp events to the game input. The onDown event will only save the starting point. On the other hand the onUp will save the end point and check if the screen was swiped. To do that we calculate the difference between both starting and end point using Phaser.Point.distance (Phaser already provides some geometric shapes and operations that you can check in the documentation: http://phaser.io/docs#geometry). If we detect a swipe, we create a Phaser.Line with the starting and end point and save the swipe for later collision checking.

FruitNinja.LevelState.prototype.start_swipe = function (pointer) {
    "use strict";
    this.start_swipe_point = new Phaser.Point(pointer.x, pointer.y);
};

FruitNinja.LevelState.prototype.end_swipe = function (pointer) {
    "use strict";
    var swipe_length, cut_style, cut;
    this.end_swipe_point = new Phaser.Point(pointer.x, pointer.y);
    swipe_length = Phaser.Point.distance(this.end_swipe_point, this.start_swipe_point);
    // if the swipe length is greater than the minimum, a swipe is detected
    if (swipe_length >= this.MINIMUM_SWIPE_LENGTH) {
        // create a new line as the swipe and check for collisions
        cut_style = {line_width: 5, color: 0xE82C0C, alpha: 1}
        cut = new FruitNinja.Cut(this, "cut", {x: 0, y: 0}, {group: "cuts", start: this.start_swipe_point, end: this.end_swipe_point, duration: 0.3, style: cut_style});
        this.swipe = new Phaser.Line(this.start_swipe_point.x, this.start_swipe_point.y, this.end_swipe_point.x, this.end_swipe_point.y);
    }
};

We will also create a Cut prefab to show it in the screen. The Cut prefab will extend Phaser.Graphics instead of Phaser.Sprite. This class allows to draw lines in the screen, which we will use to draw the cut. After drawing the line in the prefab constructor, we initialize a timer to kill it after some time, making it disappear.

By now, you can already try making some swipes and checking if the cuts are being properly draw.

cut

Fruits and bombs

We will need Fruit and Bomb prefabs to be cut in our game. However, as you can already imagine, both will have a lot of things in common. So, to avoid code repetition, we will create a generic Cuttable prefab which both Fruit and Bomb will extend and will handle all the common things.

The Cuttable prefab code is shown below. It will have a starting velocity, which will be applied in the constructor. We expect the fruits and bombs to jump on the screen and then start falling, until leaving it. So, we will set both checkWorldBounds and outOfBoundsKill properties to true. Those are Phaser properties that kill a sprite when it leaves the screen, as we want it to work.

var FruitNinja = FruitNinja || {};

FruitNinja.Cuttable = function (game_state, name, position, properties) {
    "use strict";
    FruitNinja.Prefab.call(this, game_state, name, position, properties);
    
    this.anchor.setTo(0.5);
    this.scale.setTo(5);
    
    this.game_state.game.physics.arcade.enable(this);
    
    // initiate velocity
    this.velocity = properties.velocity;
    this.body.velocity.y = -this.velocity.y;
    this.body.velocity.x = this.velocity.x;
    
    // kill prefab if it leaves screen
    this.checkWorldBounds = true;
    this.outOfBoundsKill = true;
};

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

FruitNinja.Cuttable.prototype.reset = function (position_x, position_y, velocity) {
    "use strict";
    Phaser.Sprite.prototype.reset.call(this, position_x, position_y);
    // initiate velocity
    this.body.velocity.y = -velocity.y;
    this.body.velocity.x = velocity.x;
};

Now we can create the Fruit and Bomb prefabs as follows. Notice that we only have to add a “cut” method in each one that will handle cuts. The Fruit prefab will increment a score variable, while the Bomb will end the game. This “cut” method will be called later, when we add the cutting logic. For the Fruit prefab, we will randomly select a frame from the fruits spritesheet, so we can create different kinds of fruits.

var FruitNinja = FruitNinja || {};

FruitNinja.Fruit = function (game_state, name, position, properties) {
    "use strict";
    var frame_index;
    FruitNinja.Cuttable.call(this, game_state, name, position, properties);
    
    this.frames = properties.frames;
    
    frame_index = this.game_state.rnd.between(0, this.frames.length - 1);
    this.frame = this.frames[frame_index];
    
    this.body.setSize(20, 20);
};

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

FruitNinja.Fruit.prototype.reset = function (position_x, position_y, velocity) {
    "use strict";
    var frame_index;
    FruitNinja.Cuttable.prototype.reset.call(this, position_x, position_y, velocity);
    frame_index = this.game_state.rnd.between(0, this.frames.length - 1);
    this.frame = this.frames[frame_index];
};

FruitNinja.Fruit.prototype.cut = function () {
    "use strict";
    // if a fruit is cut, increment score
    this.game_state.score += 1;
    this.kill();
};
var FruitNinja = FruitNinja || {};

FruitNinja.Bomb = function (game_state, name, position, properties) {
    "use strict";
    FruitNinja.Cuttable.call(this, game_state, name, position, properties);
    
    this.body.setSize(20, 20);
};

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

FruitNinja.Bomb.prototype.cut = function () {
    "use strict";
    // if a bomb is cut, it's game over
    this.game_state.game_over();
    this.kill();
};

Spawning fruits and bombs

To spawn fruits and bombs, we’ll do something similar to the Fruit and Bomb prefabs. Since we need one spawner for each cuttable object, we will create a generic Spawner and two more prefabs that will extend it: FruitSpawner and BombSpawner.

The Spawner code is shown below. First, a timer is created and scheduled to spawn the first prefab. The time to spawn is randomly selected between an interval. To generate a random value between two integers, we use Phaser random data generator (for more information, check Phaser documentation). After the spawn time, the timer calls a “spawn” method, that will create the prefab.

var FruitNinja = FruitNinja || {};

FruitNinja.Spawner = function (game_state, name, position, properties) {
    "use strict";
    FruitNinja.Prefab.call(this, game_state, name, position, properties);
    
    this.pool = this.game_state.groups[properties.pool];
    
    this.spawn_time = properties.spawn_time;
    
    this.velocity_x = properties.velocity_x;
    this.velocity_y = properties.velocity_y;
    
    this.spawn_timer = this.game_state.time.create();
    this.schedule_spawn();
};

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

FruitNinja.Spawner.prototype.schedule_spawn = function () {
    "use strict";
    var time;
    // add a new spawn event with random time between a range
    time = this.game_state.rnd.between(this.spawn_time.min, this.spawn_time.max);
    this.spawn_timer.add(Phaser.Timer.SECOND * time, this.spawn, this);
    this.spawn_timer.start();
};

FruitNinja.Spawner.prototype.spawn = function () {
    "use strict";
    var object_name, object_position, object, object_velocity;
    // get new random position and velocity
    object_position = new Phaser.Point(this.game_state.rnd.between(0.2 * this.game_state.game.world.width, 0.8 * this.game_state.game.world.width), this.game_state.game.world.height);
    object_velocity = this.object_velocity();
    // get first dead object from the pool
    object = this.pool.getFirstDead();
    if (!object) {
        // if there is no dead object, create a new one
        object_name = "object_" + this.pool.countLiving();
        object = this.create_object(object_name, object_position, object_velocity);
    } else {
        // if there is a dead object, reset it to the new position and velocity
        object.reset(object_position.x, object_position.y, object_velocity);
    }
    
    // schedule next spawn
    this.schedule_spawn();
};

FruitNinja.Spawner.prototype.object_velocity = function () {
    "use strict";
    var velocity_x, velocity_y;
    // generate random velocity inside a range
    velocity_x = this.game_state.rnd.between(this.velocity_x.min, this.velocity_x.max);
    velocity_y = this.game_state.rnd.between(this.velocity_y.min, this.velocity_y.max);
    return new Phaser.Point(velocity_x, velocity_y);
};

To create prefabs, we will use a pool of objects, as we used in another tutorial. A pool of objects is a group that we keep to reuse old objects instead of create new ones. So, to spawn a new preab, first we query the pool group (which is a Phaser group) for the first dead prefab in it. If there is a dead prefab, we just reset it to the new position and velocity. Otherwise, we create a new prefab using the method “create_object”. This method will be added in each Spawner we will create, to return the correct Prefab. After spawning the new prefab, we schedule the next spawn. Also, notice that the position and velocity are randomly defined inside a range, using Phaser random data generator.

The FruitSpawner and BombSpawner prefabs are shown below. We only have to add the “create_object” method, which will return a Fruit prefab for the first spawner and a Bomb in the second one. For the FruitSpawner, we have to create the Fruit with the frames we want to use from the spritesheet. In this tutorial I used frames 20, 21, 23, 35, 38. Feel free to use the ones you find best.

var FruitNinja = FruitNinja || {};

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

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

FruitNinja.FruitSpawner.prototype.create_object = function (name, position, velocity) {
    "use strict";
    // return new fruit with random frame
    return new FruitNinja.Fruit(this.game_state, name, position, {texture: "fruits_spritesheet", group: "fruits", frames: this.frames, velocity: velocity});
};
var FruitNinja = FruitNinja || {};

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

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

FruitNinja.BombSpawner.prototype.create_object = function (name, position, velocity) {
    "use strict";
    // return new bomb
    return new FruitNinja.Bomb(this.game_state, name, position, {texture: "bomb_image", group: "bombs", velocity: velocity});
};

You can already run your game with the spawner and see if it is working correctly.

fruit

Cutting fruits and bombs

We still have to make it possible to cut fruits and bombs. Back in our LevelState, we’re already detecting swipes. Now, after we detect a swipe we will check if it’s colliding with any cuttable object and, if so, cut it. To do that, we go through all living sprites in the fruits and bombs groups calling the “check_collision” method.

The “check_collision” method will check for intersections between the swipe line (which we already have) and each one of the target object boundaries. To do that, we start creating a Phaser.Rectangle with the object body coordinates and size. Then, we build a Phaser.Line for each one of this rectangle edges and check for intersection with the swipe line. To check for intersection, we will use another one of Phaser geometry operations, which check for intersection between two lines.

FruitNinja.LevelState.prototype.check_collision = function (object) {
    "use strict";
    var object_rectangle, line1, line2, line3, line4, intersection;
    // create a rectangle for the object body
    object_rectangle = new Phaser.Rectangle(object.body.x, object.body.y, object.body.width, object.body.height);
    // check for intersections with each rectangle edge
    line1 = new Phaser.Line(object_rectangle.left, object_rectangle.bottom, object_rectangle.left, object_rectangle.top);
    line2 = new Phaser.Line(object_rectangle.left, object_rectangle.top, object_rectangle.right, object_rectangle.top);
    line3 = new Phaser.Line(object_rectangle.right, object_rectangle.top, object_rectangle.right, object_rectangle.bottom);
    line4 = new Phaser.Line(object_rectangle.right, object_rectangle.bottom, object_rectangle.left, object_rectangle.bottom);
    intersection = this.swipe.intersects(line1) || this.swipe.intersects(line2) || this.swipe.intersects(line3) || this.swipe.intersects(line4);
    if (intersection) {
        // if an intersection is found, cut the object
        object.cut();
    }
};

You can already run your game and see if you can cut the fruits and bombs.

 

Keeping track of cut fruits

To finish, we have to show the score during the game. We’re already saving the cut fruits, so we just have to add a HUD item to show it. To do that, we will add an “init_hud” method in LevelState. In this method we will create a Score prefab in a fixed position and add it to the HUD group.

FruitNinja.LevelState.prototype.init_hud = function () {
    "use strict";
    var score_position, score_style, score;
    // create score prefab
    score_position = new Phaser.Point(20, 20);
    score_style = {font: "48px Arial", fill: "#fff"};
    score = new FruitNinja.Score(this, "score", score_position, {text: "Fruits: ", style: score_style, group: "hud"});
};

The Score prefab code is shown below. We only need an update method to change its text to show the current score, which corresponds to the number of cut fruits.

var FruitNinja = FruitNinja || {};

FruitNinja.Score = function (game_state, name, position, properties) {
    "use strict";
    Phaser.Text.call(this, game_state.game, position.x, position.y, properties.text, properties.style);
    
    this.game_state = game_state;
    
    this.name = name;
    
    this.game_state.groups[properties.group].add(this);
    
    this.game_state.prefabs[name] = this;
};

FruitNinja.Score.prototype = Object.create(Phaser.Text.prototype);
FruitNinja.Score.prototype.constructor = FruitNinja.Score;

FruitNinja.Score.prototype.update = function () {
    "use strict";
    // update the text to show the number of cutted fruits
    this.text = "Fruits: " + this.game_state.score;
};

Now, you can play the game while it shows how many fruits you have already cut.score

Finishing the game

With that, we finish our game. In the following tutorials we’ll add more content to our game, making it more fun to play. Tell me your opinion of this tutorial and what you would like to see next!