How to Create a Pokemon GO Game – Part 2

In the last tutorial we created the basic Phaser states for our game. Now, we are going to add the Trainer in the WorldState, and change our game to regularly spawn Pokemon. The following topics will be covered in this tutorial:

In this first tutorial I will cover the following content:

  • Creating a Trainer prefab to walk around the World
  • Creating a prefab to regularly spawn Pokemon in the World
  • Adding different species of Pokemon with different probabilities
  • Saving the Pokemon caught by the trainer, which are shown in its Pokedex

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

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.

Trainer prefab

Let’s start by creating the Trainer prefab. In the original Pokemon GO game the Trainer walks according to your GPS position. However, in this series we are only going to make it walk towards a desired position obtained when the player clicks (or touches) the screen.

In order to do that, in the constructor we need to save its walking speed, define its animations, initialize its physical body and the input event to move the Trainer.

Pokemon.Trainer = function (game_state, name, position, properties) {
    "use strict";
    var rotate_tween;
    Pokemon.Prefab.call(this, game_state, name, position, properties);
    
    this.walking_speed = +properties.walking_speed;
    
    this.animations.add("walking_down", [0, 1, 2, 3], 10, true);
    this.animations.add("walking_up", [4, 5, 6, 7], 10, true);
    this.animations.add("walking_right", [8, 9, 10, 11], 10, true);
    this.animations.add("walking_left", [12, 13, 14, 15], 10, true);
    
    this.stopped_frames = [0, 8, 12, 4, 0];
    
    this.game_state.game.physics.p2.enable(this);
    
    this.game_state.game.input.onDown.add(this.move_to, this);
    
    this.target_position = new Phaser.Point(this.position.x, this.position.y);
};

The “move_to” method will simply set a new target position for the Trainer.

Pokemon.Trainer.prototype.move_to = function (pointer) {
    "use strict";
    this.target_position.x = Math.round(pointer.position.x);
    this.target_position.y = Math.round(pointer.position.y);
};

Now the update method is the one responsible for moving the Trainer. This method starts by checking if the Trainer has to move on the y coordinate, by comparing its current y coordinate to the y coordinate of the target position. If so, we set its velocity in the y direction. Next, we do the same for the x coordinate. If no movement is necessary, we set the velocity for 0.

Finally, we play the correct animation accordingly to the Trainer velocity. If the velocity is 0 in both directions, we stop the current animation and set the current frame to the appropriate stopped frame, using the facing property of the physical body.

Pokemon.Trainer.prototype.update = function () {
    "use strict";
    var direction_y, direction_x;
    
    if (Math.abs(this.position.y - this.target_position.y) > 1) {
        direction_y = (this.position.y < this.target_position.y) ? 1 : -1;
        this.body.velocity.x = 0;
        this.body.velocity.y = direction_y * this.walking_speed;
    } else if (Math.abs(this.position.x - this.target_position.x) > 1) {
        direction_x = (this.position.x < this.target_position.x) ? 1 : -1;
        this.body.velocity.x = direction_x * this.walking_speed;
        this.body.velocity.y = 0;
    } else {
        this.body.velocity.x = 0;
        this.body.velocity.y = 0;
    }
    
    if (this.body.velocity.y > 0) {
        this.animations.play("walking_down");
    } else if (this.body.velocity.y < 0) {
        this.animations.play("walking_up");
    } else if (this.body.velocity.x > 0) {
        this.animations.play("walking_right");
    } else if (this.body.velocity.x < 0) {
        this.animations.play("walking_left");
    } else {
        this.animations.stop();
        this.frame = this.stopped_frames[this.body.facing];
    }
};

By now, you can already try adding the Trainer to the Tiled map of WorldState. You only need to remember to add the Trainer prefab to the “prefab_classes” property in WorldState.

trainer

PokemonSpawner prefab

The next step is to spawn Pokemon around the world map.

First, we will change the PokemonSpawn prefab. Until now, this prefab only had an input event to start the CatchState. We are going to change it to make it visible only when the Trainer is close, and kill the spawn after some time.

So, in the constructor we need to save the PokemonSpawn duration and detection radius. Then, we initially set it as invisible and start the kill event.

Pokemon.PokemonSpawn = function (game_state, name, position, properties) {
    "use strict";
    Pokemon.Prefab.call(this, game_state, name, position, properties);
    
    this.anchor.setTo(0.25);
    this.scale.setTo(0.25);
    
    this.duration = properties.duration;
    this.detection_radius = properties.detection_radius;
    
    this.visible = false;
    
    this.add_kill_event();
    
    // add input event to try catching this Pokemon
    this.events.onInputDown.add(this.try_catching, this);
};

The “add_kill_event” method start a timer with a random duration which will kill the spawn after some time. The random duration is obtained between the minimum and maximum values in the duration property.

Pokemon.PokemonSpawn.prototype.add_kill_event = function () {
    "use strict";
    var duration;
    duration = this.game_state.rnd.between(this.duration.min, this.duration.max);
    this.kill_timer = this.game_state.time.create();
    this.kill_timer.add(Phaser.Timer.SECOND * duration, this.kill, this);
};

The “update” method, by its turn, will check if the Trainer is close to the PokemonSpawn, in order to show it. We do that by measuring the distance between those two prefabs. If this distance is less than the detection radius we make the spawn visible and enable its inputs, so that now the player can catch it.

Pokemon.PokemonSpawn.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.visible = trainer_within_detection_radius;
    this.inputEnabled = trainer_within_detection_radius;
};

Now we are going to create a PokemonSpawner prefab, which will actually create PokemonSpawn on the map.

In order to do that we need to save in the constructor the following properties: the default properties of spawns in order to create them, the minimum and maximum spawn times in order to create a spawn timer, and a spawn distance range. In the end we create a spawn timer and call a method to schedule its next event.

Pokemon.PokemonSpawner = function (game_state, name, position, properties) {
    "use strict";
    Pokemon.Prefab.call(this, game_state, name, position, properties);
    
    this.DEFAULT_SPAWN_PROPERTIES = {
        texture: "",
        group: "spawns",
        duration: {min: 30, max: 60},
        detection_radius: 50
    };
    
    this.spawn_time_min = +properties.spawn_time_min;
    this.spawn_time_max = +properties.spawn_time_max;
    this.spawn_range = +properties.spawn_range;
    
    this.spawn_timer = this.game_state.time.create(false);
    this.schedule_spawn();
};

The “schedule_spawn” method pick a random number between the minimum and maximum spawn times and add a new event with this duration. This event will call the “spawn” method, which will create a new PokemonSpawn.

Pokemon.PokemonSpawner.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.select_pokemon, this);
    this.spawn_timer.start();
};

In order to create new spawn, we are going to use a method defined in a different file (Utils.js) that creates a prefab from a pool. The idea is to get the first dead prefab from the pool and creating a new prefab only if there is no dead one to reuse. If there is already a dead prefab, we simply reset it to the desired position.

Pokemon.create_prefab_from_pool = function (pool, prefab_constructor, game_state, prefab_name, prefab_position, prefab_properties) {
    "use strict";
    var prefab;
    // get the first dead prefab from the pool
    prefab = pool.getFirstDead();
    if (!prefab) {
        // if there is no dead prefab, create a new one
        prefab = new prefab_constructor(game_state, prefab_name, prefab_position, prefab_properties);
    } else {
        // if there is a dead prefab, reset it in the new position
        prefab.reset(prefab_position.x, prefab_position.y);
    }
    return prefab;
};

So, in the “spawn” method, we simply need to define the pool, the prefab name, its position and properties, in order to call the “create_prefab_from_pool” method. Notice that the position is calculated by picking a random distance from the spawner using the “spawn_range” property. After spawning another pokemon, we call the “schedule_spawn” method again, to schedule the next event.

Pokemon.PokemonSpawner.prototype.spawn = function (pokemon_data) {
    "use strict";
    var pool, spawn_name, distance, spawn_position, spawn_properties;
    pool = this.game_state.groups.spawns;
    spawn_name = this.name + "_spawn_" + pool.countLiving() + pool.countDead();
    distance = new Phaser.Point(this.game_state.rnd.between(-this.spawn_range, this.spawn_range), this.game_state.rnd.between(-this.spawn_range, this.spawn_range));
    spawn_position = new Phaser.Point(this.x + distance.x, this.y + distance.y);
    spawn_properties = Object.create(this.DEFAULT_SPAWN_PROPERTIES);
    spawn_properties.texture = pokemon_data.properties.texture;
    spawn_properties.pokemon_properties = pokemon_data.properties;
    Pokemon.create_prefab_from_pool(pool, Pokemon.PokemonSpawn.prototype.constructor, this.game_state, spawn_name, spawn_position, spawn_properties);
    
    this.schedule_spawn();
};

By now you can already try adding a spawner to your Tiled map and see if it is correctly spawning Pokemon. Also, check if the spawns are working correctly, by showing up only when the Trainer is close to them.

spawn

Adding different species of Pokemon

Now that we are spawning Pokemon, we want to add different species of Pokemon with different spawn probabilities.

We will describe all Pokemon in a separate JSON file like the one below (if you’re curious, I pick the Pokemon name using this generator):

[
    {
        "probability": 0.3,
        "properties": {
            "texture": "draros_image",
            "group": "pokemons",
            "anchor": {"x": 0.5, "y": 0.5},
            "frame": 1,
            "fleeing_rate": 0.3,
            "species": "Draros"
        }
    },
    {
        "probability": 0.5,
        "properties": {
            "texture": "penguine_image",
            "group": "pokemons",
            "anchor": {"x": 0.5, "y": 0.5},
            "frame": 1,
            "fleeing_rate": 0.4,
            "species": "Penguine"
        }
    },
    {
        "probability": 1.0,
        "properties": {
            "texture": "spinron_image",
            "group": "pokemons",
            "anchor": {"x": 0.5, "y": 0.5},
            "frame": 1,
            "fleeing_rate": 0.5,
            "species": "Spinron"
        }
    }
]

This file is read in the “preload” method from WorldState, and parsed in the end of the “create” method.

Pokemon.WorldState.prototype.preload = function () {
    "use strict";
    this.load.text("pokemon_probabilities", this.level_data.pokemon_probabilities);
};

this.pokemon_probabilities = JSON.parse(this.game.cache.getText("pokemon_probabilities"));

Now let’s change the PokemonSpawn and PokemonSpawner prefabs to support different species.

In the PokemonSpawn prefab we simply need to add a new property called “pokemon_properties”, which will have the properties specific to that species (obtained from the JSON file. Then, the “try_catching” method will start CatchState with the “pokemon_properties” as an extra parameter.

this.pokemon_properties = properties.pokemon_properties;

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

In the PokemonSpawner prefab we need to select the pokemon species before spawning it. So, the spawn timer now will call a “select_pokemon” method, which will pick a random number between 0 and 1. This number will be used to select the species to be spawned. We can do that by iterating through all species and choosing the first one whose probability is larger than the generated number (as long as the species probabilities are sorted in ascending order). After choosing the species, we call the “spawn” method. We also need to change the “spawn” method to set the “pokemon_properties” property accordingly to the species.

Pokemon.PokemonSpawner.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.select_pokemon, this);
    this.spawn_timer.start();
};

Pokemon.PokemonSpawner.prototype.select_pokemon = function () {
    "use strict";
    var random_number, pokemon_index, pokemon_data;
    random_number = this.game_state.rnd.frac();
    for (pokemon_index = 0; pokemon_index < this.game_state.pokemon_probabilities.length; pokemon_index += 1) {
        pokemon_data = this.game_state.pokemon_probabilities[pokemon_index];
        if (random_number < pokemon_data.probability) {
            this.spawn(pokemon_data);
            break;
        }
    }
};

Pokemon.PokemonSpawner.prototype.spawn = function (pokemon_data) {
    "use strict";
    var pool, spawn_name, distance, spawn_position, spawn_properties;
    pool = this.game_state.groups.spawns;
    spawn_name = this.name + "_spawn_" + pool.countLiving() + pool.countDead();
    distance = new Phaser.Point(this.game_state.rnd.between(-this.spawn_range, this.spawn_range), this.game_state.rnd.between(-this.spawn_range, this.spawn_range));
    spawn_position = new Phaser.Point(this.x + distance.x, this.y + distance.y);
    spawn_properties = Object.create(this.DEFAULT_SPAWN_PROPERTIES);
    spawn_properties.texture = pokemon_data.properties.texture;
    spawn_properties.pokemon_properties = pokemon_data.properties;
    Pokemon.create_prefab_from_pool(pool, Pokemon.PokemonSpawn.prototype.constructor, this.game_state, spawn_name, spawn_position, spawn_properties);
    
    this.schedule_spawn();
};

Finally, we need to change our Phaser states to allow CatchState to receive the extra parameter. First, we change BootState and LoadingState to receive the extra parameter and simply send it to the next state.

Pokemon.BootState.prototype.init = function (level_file, next_state, extra_parameters) {
    "use strict";
    this.level_file = level_file;
    this.next_state = next_state;
    this.extra_parameters = extra_parameters;
};

Pokemon.BootState.prototype.create = function () {
    "use strict";
    var level_text, level_data;
    // parse the level file as a JSON object and send its data to LoadingState
    level_text = this.game.cache.getText("level1");
    level_data = JSON.parse(level_text);
    this.game.state.start("LoadingState", true, false, level_data, this.next_state, this.extra_parameters);
};
Pokemon.LoadingState.prototype.init = function (level_data, next_state, extra_parameters) {
    "use strict";
    this.level_data = level_data;
    this.next_state = next_state;
    this.extra_parameters = extra_parameters;
};

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

Then, in CatchState we can save the pokemon properties from the extra parameter. Now, instead of creating the Pokemon in CatchState from the JSON file, we are going to manually create it in the end of the “create” method (and remove it from the JSON file). By doing so, we can create the Pokemon with the correct properties according to its species.

Pokemon.CatchState.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;
    
    // 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 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);
    
    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);
    
    // 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_data = {
        type: "pokemon",
        position: {x: 0.5, y: 0.6},
        properties: this.pokemon_properties
    }
    this.create_prefab("pokemon", pokemon_data);
};

By now you can try playing with different Pokemon species. Check if they are being spawned correctly, and if the Pokemon in CatchState are being properly created.

Showing the Pokedex

The last thing we are going to do in this tutorial is adding a Pokedex prefab to show the currently caught Pokemon.

In order to show the caught Pokemon we will create a PokemonSprite prefab, which will simply show the Pokemon and its species. So, in the constructor we need to create a TextPrefab, which must be killed in the “kill” method.

var Pokemon = Pokemon || {};

Pokemon.PokemonSprite = function (game_state, name, position, properties) {
    "use strict";
    Pokemon.Prefab.call(this, game_state, name, position, properties);
    var text_position;
    
    this.anchor.setTo(0.5);
    this.scale.setTo(0.25);
    
    text_position = new Phaser.Point(this.x, this.y + (this.height / 2) + 10);
    this.message_text = new Pokemon.TextPrefab(this.game_state, this.name + "_text", text_position, properties.text_properties);
    this.message_text.anchor.setTo(0.5);
};

Pokemon.PokemonSprite.prototype = Object.create(Pokemon.Prefab.prototype);
Pokemon.PokemonSprite.prototype.constructor = Pokemon.PokemonSprite;

Pokemon.PokemonSprite.prototype.kill = function () {
    "use strict";
    Phaser.Sprite.prototype.kill.call(this);
    this.message_text.kill();
};

Now the Pokedex will have the default properties for PokemonSprite, which will be changed according to the Pokemon species. In the constructor we are also going to initialize the position for the first PokemonSprite and add an input event to hide the Pokedex when it is clicked.

Pokemon.Pokedex = function (game_state, name, position, properties) {
    "use strict";
    Pokemon.Prefab.call(this, game_state, name, position, properties);
    
    this.anchor.setTo(0.5);
    
    this.DEFAULT_POKEMON_SPRITE_PROPERTIES = {
        texture: "",
        group: "pokemon_sprites",
        text_properties: {
            text: "",
            group: "hud",
            style: {
                font: "14px Arial",
                fill: "#000"
            }
        }
    };
    
    this.initial_pokemon_sprite_position = new Phaser.Point(this.x - (this.width / 2) + 50, this.y - (this.height / 2) + 50);
    
    // add input event to call game state method
    this.events.onInputDown.add(this.hide, this);
    
    this.visible = false;
};

The “show” method will make the Pokedex visible, enable its input events (so that we can hide it later), and show all caught Pokemon (saved in a “caught_pokemon” array in the game). We are going to create the PokemonSprites using the “create_prefab_from_pool” method, so we only need to define the prefab name, position and properties. Notice that we only need to change the texture and text in the default properties, while the position starts by the initial one and is updated after each iteration.

Pokemon.Pokedex.prototype.show = function () {
    "use strict";
    var pokemon_sprite, pokemon_sprite_position;
    
    this.visible = true;
    this.inputEnabled = true;
    
    pokemon_sprite_position = new Phaser.Point(this.initial_pokemon_sprite_position.x, this.initial_pokemon_sprite_position.y);
    this.game_state.game.caught_pokemon.forEach(function (pokemon) {
        var pool, pokemon_sprite_name, pokemon_sprite_properties;
        pool = this.game_state.groups.pokemon_sprites;
        pokemon_sprite_name = "pokemon_sprite_name_" + pool.countLiving() + pool.countDead();
        pokemon_sprite_properties = Object.create(this.DEFAULT_POKEMON_SPRITE_PROPERTIES);
        pokemon_sprite_properties.texture = pokemon.texture;
        pokemon_sprite_properties.text_properties.text = pokemon.species;
        
        pokemon_sprite = Pokemon.create_prefab_from_pool(pool, Pokemon.PokemonSprite.prototype.constructor, this.game_state, pokemon_sprite_name, pokemon_sprite_position, pokemon_sprite_properties);
        pokemon_sprite_position.x += pokemon_sprite.width + 50;
        if (pokemon_sprite_position.x > (this.width - 50)) {
            pokemon_sprite_position.x = this.initial_pokemon_sprite_position.x;
            pokemon_sprite_position.y += pokemon_sprite.height + 50;
        }
    }, this);
};

The “hide” method, by its turn, will simply make the Pokedex invisible, disable its input events and kill all created PokemonSprites.

Pokemon.Pokedex.prototype.hide = function () {
    "use strict";
    this.visible = false;
    this.inputEnabled = false;
    
    this.game_state.groups.pokemon_sprites.forEach(function (pokemon_sprite) {
        pokemon_sprite.kill();
    }, this);
};

There are two things left to do. First, we need to add all caught Pokemon to the “caught_pokemon” array. The second thing is adding a button to show the Pokedex.

In order to add caught Pokemon to the “caught_pokemon” array we need to change the Pokemon prefab. Once a Pokemon is caught we check if it is the first time this species was caught. If so, we add it to the array. The “already_caught” method simply checks if there is already a Pokemon with the same species in the array.

Pokemon.Pokemon.prototype.catch = function () {
    "use strict";
    var catch_message;
    // kill the Pokemon and show the catch message box
    this.kill();
    
    if (!this.already_caught()) {    
        this.game_state.game.caught_pokemon.push({species: this.species, texture: this.texture_key});
    }
    
    catch_message = new Pokemon.MessageBox(this.game_state, "catch_message", {x: this.game_state.game.world.centerX, y: this.game_state.game.world.centerY}, this.MESSAGE_PROPERTIES);
    catch_message.message_text.text = "Gotcha!";
};

Pokemon.Pokemon.prototype.already_caught = function () {
    "use strict";
    var caught = false;
    this.game_state.game.caught_pokemon.forEach(function (pokemon) {
        if (pokemon.species === this.species) {
            caught = true;
        }
    }, this);
    return caught;
};

Finally, to create the Pokedex button we are going to create a Button prefab which will simply extend Phaser.Button calling a method from “game_state” (defined in the “callback” property) when clicked. Then, we can add a Button prefab in the Tiled map to call the “show_pokedex” method in WorldState. This method will simply call the “show” method from the pokedex.

var Pokemon = Pokemon || {};

Pokemon.Button = function (game_state, name, position, properties) {
    "use strict";
    Phaser.Button.call(this, game_state.game, position.x, position.y, properties.texture, game_state[properties.callback], game_state);
    
    this.game_state = game_state;
    
    this.name = name;
    
    this.game_state.groups[properties.group].add(this);
    
    this.anchor.setTo(0.5);
    
    this.game_state.prefabs[name] = this;
};

Pokemon.Button.prototype = Object.create(Phaser.Button.prototype);
Pokemon.Button.prototype.constructor = Pokemon.Button;
Pokemon.WorldState.prototype.show_pokedex = function () {
    "use strict";
    this.prefabs.pokedex.show();
};

Now you can try catching some Pokemon and check if your Pokedex is being updated. Try catching the same species more than once and see if the Pokedex is only showing each species once.

pokedex

And that concludes this tutorial. In the next one we are going to add Pokestops to get Pokeballs, as well as different types of Pokeballs with different catching rates.