How to Create a Pokemon GO Game – Part 3

In the last tutorial we added the Trainer and different Pokemon species to our game. Now we are going to limit the number of pokeballs for the player, and add different types of pokeball with different catching rates. To get new pokeballs the player will have to interact with pokestops. The following topics will be covered in this tutorial:

  • Limiting the number of pokeballs
  • Adding different types of pokeball with different catching rates
  • Adding pokestops where the player can collect pokeballs
  • Adding a title screen

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
  • Creating maps using Tiled

Become a Game Developer 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.

Limiting the number of pokeballs

The first thing towards limiting the number of pokeballs is initializing it in main.js. Since it will be used by different game states, we will save it in the game object.

var Pokemon = Pokemon || {};


var game = new Phaser.Game(320, 640, Phaser.CANVAS);

game.caught_pokemon = [];
game.number_of_pokeballs = 0;

game.state.add("BootState", new Pokemon.BootState());
game.state.add("LoadingState", new Pokemon.LoadingState());
game.state.add("TitleState", new Pokemon.TitleState());
game.state.add("WorldState", new Pokemon.WorldState());
game.state.add("CatchState", new Pokemon.CatchState());
game.state.start("BootState", true, false, "assets/levels/title_screen.json", "TitleState");

Now, in the CatchState we want to show the current number of pokeballs below the pokeball prefab. We can do that by creating a TextPrefab below the pokeball, and setting its text to be the current number of pokeballs. This is done in the end of the constructor of the Pokeball Prefab.

text_position = new Phaser.Point(this.x, this.y + this.height * 0.7);
    this.number_of_pokeballs_text = new Pokemon.TextPrefab(this.game_state, "number_of_pokeballs", text_position, this.TEXT_PROPERTIES);
    this.number_of_pokeballs_text.text = this.game_state.game.number_of_pokeballs;

We need to keep this text updated. So, after throwing a Pokeball we need to decrease the number of pokeballs and update the text.

Pokemon.Pokeball.prototype.throw = function () {
    "use strict";
    var distance_to_initial_position;
    
    // stop draggin the pokeball
    this.dragging = false;
    
    // throw the pokeball if the distance to the initial position is above the threshold
    distance_to_initial_position = new Phaser.Point(this.x - this.initial_position.x, this.y - this.initial_position.y);
    if (distance_to_initial_position.getMagnitude() > this.THROW_THRESHOLD) {
        this.game_state.game.number_of_pokeballs -= 1;
        this.number_of_pokeballs_text.text = this.game_state.game.number_of_pokeballs;
        distance_to_initial_position.normalize();
        // initialize the pokeball physical body
        this.init_body();
        this.body.velocity.x = -distance_to_initial_position.x * this.pokeball_speed;
        this.body.velocity.y = -distance_to_initial_position.y * this.pokeball_speed;
    } else {
        this.reset(this.initial_position.x, this.initial_position.y);
    }
};

Also, if the Pokemon is not caught after throwing the Pokeball, we reset the Pokeball to its initial position. We are going to override the reset method to also check if this was the last pokeball. If so, we need to return to WorldState.

Pokemon.Pokeball.prototype.reset = function (x, y) {
    "use strict";
    Phaser.Sprite.prototype.reset.call(this, x, y);
    
    if (this.game_state.game.number_of_pokeballs === 0) {
        this.game_state.return_to_world();
    }
};

Before moving on we need to change the PokemonSpawn prefab to only start CatchState if the current number of pokeballs is greater than zero.

Pokemon.PokemonSpawn.prototype.try_catching = function () {
    "use strict";
    // start CatchState
    if (this.game_state.game.number_of_pokeballs > 0) {
        this.game_state.game.state.start("BootState", true, false, "assets/levels/catch_level.json", "CatchState", {pokemon_properties: this.pokemon_properties});
    }
};

By now you can try playing and checking if CatchState is showing the current number of pokeballs. Try losing all pokeballs to see if it returns to WorldState. Also, try clicking in a PokemonSpawn without pokeballs.

number_of_pokeballs

Adding different types of pokeball

Now that we are limiting the number of pokeballs, let’s add different types of pokeball.

First, let’s change the number of pokeballs to be an object with three different types of pokeball: pokeball, greatball and ultraball.

game.number_of_pokeballs = this.game.number_of_pokeballs || {pokeball: 0, greatball: 1, ultraball: 2};

The other parts of code that use this variable also need to be changed.

For example, in the PokemonSpawn prefab we need to check if the sum of all pokeballs is greater than zero.

Pokemon.PokemonSpawn.prototype.try_catching = function () {
    "use strict";
    // start CatchState
    if (this.game_state.game.number_of_pokeballs.pokeball +
        this.game_state.game.number_of_pokeballs.greatball +
        this.game_state.game.number_of_pokeballs.ultraball > 0) {
        this.game_state.game.state.start("BootState", true, false, "assets/levels/catch_level.json", "CatchState", {pokemon_properties: this.pokemon_properties});
    }
};

In the Pokeball prefab we need to add a “type” property. This property will be used to access the “number_of_pokeballs” object. Also, now in the “reset” method we are going to return to WorldState only if the sum of all pokeballs is zero. If the player is out of only the current pokeball, we change its alpha to 0.5.

this.type = properties.type;

this.number_of_pokeballs_text.text = this.game_state.game.number_of_pokeballs[this.type];

if (this.game_state.game.number_of_pokeballs[this.type] === 0) {
        this.alpha = 0.5;
}
Pokemon.Pokeball.prototype.reset = function (x, y) {
    "use strict";
    Phaser.Sprite.prototype.reset.call(this, x, y);
    
    if (this.game_state.game.number_of_pokeballs[this.type] === 0) {
        this.alpha = 0.5;
    }
    
    if (this.game_state.game.number_of_pokeballs.pokeball +
            this.game_state.game.number_of_pokeballs.greatball +
            this.game_state.game.number_of_pokeballs.ultraball === 0) {
        this.game_state.return_to_world();
    }
};

Now, in the CatchState level file (catch_level.json) we are going to add the three types of pokeball, each one with its own “type” and “catching_rate” properties. Also, we are going to add a Button prefab called “switch_pokeball”, which will call a “switch_pokeball” method in CatchState.

"pokeball": {
            "type": "pokeball",
            "position": {"x": 0.5, "y": 0.8},
            "properties": {
                "texture": "pokeball_image",
                "group": "pokeballs",
                "anchor": {"x": 0.5, "y": 0.5},
                "pokeball_speed": 300,
                "catching_rate": 0.5,
                "type": "pokeball"
            }
        },
        "greatball": {
            "type": "pokeball",
            "position": {"x": 0.5, "y": 0.8},
            "properties": {
                "texture": "greatball_image",
                "group": "pokeballs",
                "anchor": {"x": 0.5, "y": 0.5},
                "pokeball_speed": 300,
                "catching_rate": 0.75,
                "type": "greatball"
            }
        },
        "ultraball": {
            "type": "pokeball",
            "position": {"x": 0.5, "y": 0.8},
            "properties": {
                "texture": "ultraball_image",
                "group": "pokeballs",
                "anchor": {"x": 0.5, "y": 0.5},
                "pokeball_speed": 300,
                "catching_rate": 0.9,
                "type": "ultraball"
            }
        },
        "switch_pokeball": {
            "type": "button",
            "position": {"x": 0.5, "y": 0.95},
            "properties": {
                "texture": "pokeball_image",
                "group": "hud",
                "anchor": {"x": 0.5, "y": 0.5},
                "scale": {"x": 0.4, "y": 0.4},
                "callback": "switch_pokeball"
            }
        }

Now we need to implement the logic to switch pokeballs. First, we are going to add an “enable” method to the Pokeball prefab. This method will be called when switching a pokeball, and will change the “visible”, “inputEnabled” and “checkWorldBounds” properties. The idea is to make a pokeball visible if it is the current one. Also, we need to enable its events only if its the current one and the number of pokeballs of its type is greater than zero. By default, all pokeballs are disabled in the constructor.

Pokemon.Pokeball.prototype.enable = function (enable) {
    "use strict";
    this.visible = enable;
    this.inputEnabled = enable && (this.game_state.game.number_of_pokeballs[this.type] > 0);
    this.checkWorldBounds = enable && (this.game_state.game.number_of_pokeballs[this.type] > 0);
    this.number_of_pokeballs_text.visible = enable;
};

Finally, we need to implement the “switch_pokeball” method in CatchState. But first, in the end of the “create” method we are going to add one array with the pokeball types and one variable with the index of the current one. Then, we enable the first pokeball.

this.pokeball_types = ["pokeball", "greatball", "ultraball"];
    this.current_pokeball_index = 0;
    this.prefabs[this.pokeball_types[this.current_pokeball_index]].enable(true);

Now the “switch_pokeball” method will simply disable the current pokeball, increment the index (limited by the “pokeball_types” array size) and enable the next pokeball.

Pokemon.CatchState.prototype.switch_pokeball = function () {
    "use strict";
    this.prefabs[this.pokeball_types[this.current_pokeball_index]].enable(false);
    this.current_pokeball_index = (this.current_pokeball_index + 1) % this.pokeball_types.length;
    this.prefabs[this.pokeball_types[this.current_pokeball_index]].enable(true);
};

By now you can try playing and switching pokeballs. Notice that each pokeball type has its own texture, so you can easily see that they are changing.

types_of_pokeball

Adding pokestops

The next step is adding pokestops where the player can collect new pokeballs.

So, let’s start by creating a Pokestop prefab. In the constructor we need to save its properties such as “reset_time” and “detection_radius”. We also are going to add an input event to collect pokeballs and create a reset timer, which will be dispatched after every input event.

Pokemon.Pokestop = function (game_state, name, position, properties) {
    "use strict";
    var text_position;
    Pokemon.Prefab.call(this, game_state, name, position, properties);
    
    this.INITIAL_POKEBALL_POSITION = new Phaser.Point(this.game_state.game.width - 50, 50);
    this.POKEBALL_TIME = 2;
    
    this.reset_time = +properties.reset_time;
    this.detection_radius = +properties.detection_radius;
    
    this.events.onInputDown.add(this.get_pokeballs, this);
    
    this.reset_timer = this.game_state.game.time.create(false);
};

The “update” method will check if the trainer is close to the pokestop, similarly to what we did with the PokemonSpawn. The only difference is that now we are only going to change the “inputEnabled” property, and the pokestop will be always visible.

Pokemon.Pokestop.prototype.update = function () {
    "use strict";
    var distance_to_trainer, trainer_within_detection_radius;
    distance_to_trainer = this.position.distance(this.game_state.prefabs.trainer.position);
    trainer_within_detection_radius = distance_to_trainer <= this.detection_radius;
    this.inputEnabled = trainer_within_detection_radius;
};

The “get_pokeballs” method needs to randomly choose the type and number of pokeballs the player will collect. This will be similar to what we are already doing to spawn Pokemon. So, first we are going to move the code that choose a random Pokemon to the Utils.js file, and put it in a function called “choose_randomly. This method will generate a random number and pick the first element in an array whose probability is greater than the generated random number.

Pokemon.choose_randomly = function (rnd, probabilities) {
    "use strict";
    var random_number, element_index, element;
    random_number = rnd.frac();
    for (element_index = 0; element_index < probabilities.length; element_index += 1) {
        element = probabilities[element_index];
        if (random_number < element.probability) {
            return element;
        }
    }
};

Now, the “get_pokeballs” method will simply make use of this function. First, we are going to change the pokestop tint, to show that it has been used. Then we randomly choose a number of pokeballs to spawn, from an array containing this probabilities. Then, for each collected pokeball, we randomly choose its type, from another array of probabilities. In the end we disable the input events, call a method to show the collected pokeballs and dispatch the reset timer.

Pokemon.Pokestop.prototype.get_pokeballs = function () {
    "use strict";
    var number_of_pokeballs, pokeball_index, pokeball, pokeballs_to_show;
    
    this.tint = 0xff0000;
    
    number_of_pokeballs = Pokemon.choose_randomly(this.game_state.rnd, this.game_state.pokeball_probabilities.number_of_pokeballs);
    
    pokeballs_to_show = [];
    for (pokeball_index = 0; pokeball_index < number_of_pokeballs.number; pokeball_index += 1) {
        pokeball = Pokemon.choose_randomly(this.game_state.rnd, this.game_state.pokeball_probabilities.type_of_pokeball);
        this.game_state.game.number_of_pokeballs[pokeball.type] += 1;
        pokeballs_to_show.push(pokeball.type);
    }

    this.inputEnabled = false;
    
    this.show_pokeballs(pokeballs_to_show);
    
    this.reset_timer.add(this.reset_time * Phaser.Timer.SECOND, this.reset_pokestop, this);
    this.reset_timer.start();
};

The “show_pokeballs” method will create a Phaser.Sprite for each collected pokeball, so that the player can see what he collected. For each sprite we are going to add an event which will kill it after some time.

Pokemon.Pokestop.prototype.show_pokeballs = function (pokeballs_to_show) {
    "use strict";
    var pokeball_index, pokeball_position, pokeball, pokeball_kill_timer;
    pokeball_position = new Phaser.Point(this.INITIAL_POKEBALL_POSITION.x, this.INITIAL_POKEBALL_POSITION.y);
    pokeball_kill_timer = this.game_state.game.time.create();
    pokeballs_to_show.forEach(function(pokeball_type) {
        pokeball = new Phaser.Sprite(this.game_state.game, pokeball_position.x, pokeball_position.y, pokeball_type + "_image");
        pokeball.anchor.setTo(0.5);
        pokeball.scale.setTo(0.3);
        this.game_state.groups.hud.add(pokeball);
        
        pokeball_kill_timer.add(this.POKEBALL_TIME * Phaser.Timer.SECOND, pokeball.kill, pokeball);
        
        pokeball_position.y += 1.5*pokeball.height;
    }, this);
    pokeball_kill_timer.start();
};

The “reset_pokestop” method, by its turn, will simply restore the tint value, enable input events and stop the “reset_timer”.

Pokemon.Pokestop.prototype.reset_pokestop = function () {
    "use strict";
    this.inputEnabled = true;
    this.tint = 0xffffff;
    this.reset_timer.stop();
};

Now we need to create the probabilities arrays and add them to our “game_state”. The probabilities will be saved in a “pokeball_probabilities.json” file like the following one. There will be two arrays: one for the number of pokeballs and one for the type of each pokeball.

{
    "number_of_pokeballs": [
        {"number": 3, "probability": 0.2},
        {"number": 2, "probability": 0.5},
        {"number": 1, "probability": 1.0}
    ],
    "type_of_pokeball": [
        {"type": "pokeball", "probability": 0.5},
        {"type": "greatball", "probability": 0.8},
        {"type": "ultraball", "probability": 1.0}
    ]
}

Then, in the “preload” method of WorldState we need to load this text file. Also, in the end of the “create” method we need to parse it as a JSON file and save it in a “pokeball_probabilities” object.

Pokemon.WorldState.prototype.preload = function () {
    "use strict";
    this.load.text("pokemon_probabilities", this.level_data.pokemon_probabilities);
    this.load.text("pokeball_probabilities", this.level_data.pokeball_probabilities);
};
this.pokeball_probabilities = JSON.parse(this.game.cache.getText("pokeball_probabilities"));

By now, you can try adding a pokestop in your Tiled map and try collecting some pokeballs. Try different configurations of probabilities to check if its working correctly. Also, try collecting pokeballs when the trainer is too far away and when the pokestop is still resetting, to see if it works.

pokestop

Adding title screen

The last thing we are going to add is a title screen for our game. This will be a very simple one, which will only show the game title and will have an input event to start the game.

TitleState will also be loaded from a JSON file like CatchState. So, we are going to create a JSONLevelState which will be responsible for doing that. Then, both CatchState and TitleState will extend it, implementing only the different things. The JSONLevelState will only save the level data, set the game scale, create groups and prefabs.

var Pokemon = Pokemon || {};

Pokemon.JSONLevelState = function () {
    "use strict";
    Phaser.State.call(this);
    
    this.prefab_classes = {
        
    };
};

Pokemon.JSONLevelState.prototype = Object.create(Phaser.State.prototype);
Pokemon.JSONLevelState.prototype.constructor = Pokemon.JSONLevelState;

Pokemon.JSONLevelState.prototype.init = function (level_data, extra_parameters) {
    "use strict";
    this.level_data = level_data;
    
    this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
    this.scale.pageAlignHorizontally = true;
    this.scale.pageAlignVertically = true;
};

Pokemon.JSONLevelState.prototype.create = function () {
    "use strict";
    var group_name, prefab_name, pokemon_data;
    
    // 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]);
        }
    }
};

Pokemon.JSONLevelState.prototype.create_prefab = function (prefab_name, prefab_data) {
    "use strict";
    var prefab_position, prefab;
    // create object according to its type
    if (this.prefab_classes.hasOwnProperty(prefab_data.type)) {
        if (prefab_data.position.x > 0 && prefab_data.position.x <= 1) {
            // position as percentage
            prefab_position = new Phaser.Point(prefab_data.position.x * this.game.world.width,
                                              prefab_data.position.y * this.game.world.height);
        } else {
            // position as absolute number
            prefab_position = prefab_data.position;
        }
        prefab = new this.prefab_classes[prefab_data.type](this, prefab_name, prefab_position, prefab_data.properties);
    }
    return prefab;
};

Now CatchState will only implement its specific behavior, such as: creating the Pokemon prefab, controlling the current pokeball type, and returning to WorldState.

var Pokemon = Pokemon || {};

Pokemon.CatchState = function () {
    "use strict";
    Pokemon.JSONLevelState.call(this);
    
    this.prefab_classes = {
        "background": Pokemon.Prefab.prototype.constructor,
        "pokeball": Pokemon.Pokeball.prototype.constructor,
        "pokemon": Pokemon.Pokemon.prototype.constructor,
        "button": Pokemon.Button.prototype.constructor
    };
};

Pokemon.CatchState.prototype = Object.create(Pokemon.JSONLevelState.prototype);
Pokemon.CatchState.prototype.constructor = Pokemon.CatchState;

Pokemon.CatchState.prototype.init = function (level_data, extra_parameters) {
    "use strict";
    Pokemon.JSONLevelState.prototype.init.call(this, level_data, extra_parameters);
    
    // start physics system
    this.game.physics.startSystem(Phaser.Physics.P2JS);
    this.game.physics.arcade.gravity.y = 0;
    
    this.pokemon_properties = extra_parameters.pokemon_properties;
};

Pokemon.CatchState.prototype.create = function () {
    "use strict";
    var pokemon_data;
    
    this.collision_groups = {};
    this.level_data.collision_groups.forEach(function (collision_group_name) {
        this.collision_groups[collision_group_name] = this.game.physics.p2.createCollisionGroup();
    }, this);
    
    Pokemon.JSONLevelState.prototype.create.call(this);
    
    pokemon_data = {
        type: "pokemon",
        position: {x: 0.5, y: 0.6},
        properties: this.pokemon_properties
    }
    this.create_prefab("pokemon", pokemon_data);
    
    this.pokeball_types = ["pokeball", "greatball", "ultraball"];
    this.current_pokeball_index = 0;
    this.prefabs[this.pokeball_types[this.current_pokeball_index]].enable(true);
};

Pokemon.CatchState.prototype.return_to_world = function () {
    "use strict";
    this.game.state.start("BootState", true, false, "assets/levels/world_level.json", "WorldState");
};

Pokemon.CatchState.prototype.switch_pokeball = function () {
    "use strict";
    this.prefabs[this.pokeball_types[this.current_pokeball_index]].enable(false);
    this.current_pokeball_index = (this.current_pokeball_index + 1) % this.pokeball_types.length;
    this.prefabs[this.pokeball_types[this.current_pokeball_index]].enable(true);
};

Now, TitleState will simply add an input event in the end of the “create” method. This event will start WorldState, in order to start the game.

var Pokemon = Pokemon || {};

Pokemon.TitleState = function () {
    "use strict";
    Pokemon.JSONLevelState.call(this);
    
    this.prefab_classes = {
        "text": Pokemon.TextPrefab.prototype.constructor
    };
};

Pokemon.TitleState.prototype = Object.create(Pokemon.JSONLevelState.prototype);
Pokemon.TitleState.prototype.constructor = Pokemon.TitleState;

Pokemon.TitleState.prototype.create = function () {
    "use strict";
    var pokemon_data;
    Pokemon.JSONLevelState.prototype.create.call(this);
    
    this.game.input.onDown.add(this.start_game, this);
};

Pokemon.TitleState.prototype.start_game = function () {
    "use strict";
    this.game.state.start("BootState", true, false, "assets/levels/world_level.json", "WorldState");
};

The last thing we have to do is creating the JSON file for TitleState. This level will only have the title message and a start message.

{
    "assets": {
        
    },
    "groups": [
        "hud"
    ],
    "prefabs": {
        "title": {
            "type": "text",
            "position": {"x": 0.5, "y": 0.5},
            "properties": {
                "anchor": {"x": 0.5, "y": 0.5},
                "group": "hud",
                "text": "Phasermon GO",
                "style": {"font": "32px Arial", "fill": "#FFF"}
            }
        },
        "start_message": {
            "type": "text",
            "position": {"x": 0.5, "y": 0.7},
            "properties": {
                "anchor": {"x": 0.5, "y": 0.5},
                "group": "hud",
                "text": "Tap screen to start",
                "style": {"font": "20px Arial", "fill": "#FFF"}
            }
        }
    }
}

By now you can try playing the game with the title screen, and checking if its’s correctly starting the game.

title_screen

And that concludes this tutorial. In the next (and last) one, we are going to save the player pokeballs and caught Pokemon in an online database. Also, we will need to add authentication in order to access the saved data.