⚡ Pokémon
Few franchises have achieved the degree of fame as Pokémon, short for “Pocket Monsters”, a Japanese monster-catching phenomenon that took the world by storm in the late 90s. Even to this day, Pokémon is hugely successful, with games, movies, and various other forms of merchandise selling like crazy. The game formula itself is an addicting take on the JRPG, where the player can not only fight random Pokémon in the wild but also recruit them to be in their party at all times, where they can level up, learn new abilities, and even evolve.
This proof of concept demonstrates basic GUI usage, random encounters, and Pokémon that the player can fight and defeat with their own Pokémon.

🎯 Objectives
Section titled “🎯 Objectives”- StateStacks: We’ll see how a StateStack, which supports running multiple states in parallel, is like a more advanced version of a State Machine.
- GUIs: Graphical User Interfaces, or GUIs for short, are what bring addition player interactivity into our games. GUIs can include panels, scrollbars, textboxes, and many more visual elements for navigating a game.
- Turn-Based Systems: We’ll implement our battle mechanics using a turn-based system, which is a core feature of Pokémon and other RPGs.
- RPG Mechanics: Leveling up, experience, damage calculations - these are all a part of the RPG experience and we’ll be taking a look at how to implement these features today.
🔨 Setup
Section titled “🔨 Setup”-
Clone the repo (or download the zip) for today’s lecture, which you can find here.
-
Open the repo in Visual Studio Code.
-
Start Visual Studio Code’s “Live Server” extension. If you don’t have it installed:
-
Click on the extensions icons in the left-hand side navigation.
-
Search for “Live Server”.
-
Click install next to the extension by “Ritwick Dey”. You may have to reload the window.

-
Once it’s installed, click “Go Live” on the bottom right of the window. This should start the server and automatically open a new tab in your browser at
http://127.0.0.1:5500/(or whatever port it says on your machine).- The files the server serves will be relative to the directory you had open in VSC when you hit “Go Live”.
-
🌅 Pokémon-0 (The “Day-0” Update)
Section titled “🌅 Pokémon-0 (The “Day-0” Update)”In Pokémon-0, we create the tilemap from a map configuration file and implement tile-based movement for our player character.
Map Files
Section titled “Map Files”A map file is simply a file (JSON in our case) that contains a list of numbers that will represent which sprite from the sprite sheet to render in the game. Here’s a small example to get us started:
[ [0, 1, 2], [3, 4, 5], [6, 7, 8]]This is a 2D array representing a small map of 3x3 tiles where [0][0] is the top left and [2][2] is the bottom right. This makes it easy to move up/down/left/right around the map since all you have to do is +/- 1 from either the X or the Y relative to where you currently are. While this may be the “simpler” implementation, the author of How to Make an RPG claims that it is actually more computationally/memory efficient to store this data as a 1D array:
[0, 1, 2, 3, 4, 5, 6, 7, 8]or
[0, 1, 2, 3, 4, 5, 6, 7, 8]Here, [0] is the top left and [8] is the bottom right. How can we grab the desired tile based on an X and Y set of coordinates now? The formula is surprisingly simple: tile = x + y * width! In our example above, width = 3. If we wanted the top left tile, (0, 0), we would calculate 0 + 0 * 3 which gives us the index 0. Similarly, the bottom right tile, (2, 2), can be retrieved by performing 2 + 2 * 3 giving us the index 8. See? Easy!
Important Code
Section titled “Important Code”-
./config/map.json: A 15x11 map whose values correspond to the first few tiles in./assets/tiles.js. This file is read inmain.jsand the contents is passed toMap. -
./services/Map.js: The container for holding a 1D array ofTileobjects that will represent the game world.getTile(x, y): Implements the formula in the previous section to retrieve one tile from the array based on a givenxandy.generateTiles(layerData, sprites): Uses the array elements frommap.jsonas the sprite IDs for the newly createdTileobjects which comprise the map.
-
./services/Tile.js: Represents one tile on theMapand on the screen. -
./entities/Player.js: The character being controlled by the player. What’s unique to Pokémon is that the character’s movement is locked to the grid unlike Zelda where the character could move anywhere on the map.-
./states/player/PlayerWalkingState.js: We achieve this grid-locked movement by tweening the character’s position when the player moves the character. This forces the character to smoothly transition from one tile to another over a set period of time. Only once the tween is over can the player move the character to another tile.PlayerWalkingState.js tweenMovement(x, y) {this.isMoving = true;timer.tween(this.player.canvasPosition,['x', 'y'],[x * Tile.SIZE, y * Tile.SIZE],0.25,() => this.isMoving = false);}To illustrate this further, we’ve drawn a grid on the ground so that it is clearly evident that the player is always locked to the grid when moving. Try briefly tapping a direction key and notice that the character does not stop when you let go like the character in Zelda did!
-
./states/player/PlayerIdlingState.js: Simply renders the character standing still in whatever direction they’re currently facing.
-
🗺️ Pokémon-1 (The “Map” Update)
Section titled “🗺️ Pokémon-1 (The “Map” Update)”In Pokémon-1, we generate a more sophisticated map with the help of the Tiled map editor. Using this program, we are able to generate maps that have multiple layers that can help us with things like collision detection and rendering order.
Map Editor
Section titled “Map Editor”Open the ./assets/images/tiles.png sprite sheet and notice how large it is - 256x16064 pixels! Identifying the grass tile at index 2 is much easier to discern than the tree tile at index 431. Let’s learn how we can simplify this process.
-
If you don’t already have it installed, head over to https://www.mapeditor.org/ and download+install the Tiled map editor.
- This is an excellent free and open source piece of software that we can leverage to make our lives easier when creating tilemaps.
-
Click
File > New > New Mapand enter the following configuration:
- Change the name to whatever you’d like and save it to the
./Pokémon-1/assets/mapsfolder.
- Change the name to whatever you’d like and save it to the
-
You should now see a new blank map:

-
Click
File > New > New Tilesetand browse to the location of./Pokémon-1/assets/images/tiles.png:
- Change the name to whatever you’d like and save it to the
./Pokémon-1/assets/mapsfolder.
- Change the name to whatever you’d like and save it to the
-
You should now see the tileset broken up automatically into tiles:

-
There should be two tabs at the top. The first one is the map editor, and the second one (screenshot above) is the tileset. Switch to the first tab so that we can start making our map!
Creating a Map
Section titled “Creating a Map”-
At the top you’ll find several tools at your disposal. For our purposes, we’ll be sticking with the rectangle tool:

-
Select the rectangle tool, then select a grass tile from the top row of the tileset on the right you want to paint. In the main map grid view, drag your mouse from the top left of the map to the bottom right, and you should get something like this:

-
That looks too uniform! To vary it up, we could manually choose different grass tiles and paint them in various spots on the map, but there is an easier way.
-
With the rectangle tool selected, select the dice icon 🎲 from the toolbar as well.
-
Click and drag on the tileset to select multiple tiles:

-
Just as before, in the main map grid view, drag your mouse from the top left of the map to the bottom right, and you should get something less uniform:

-
Adding Layers
Section titled “Adding Layers”For our Pokémon game, we’ll want to have 3 distinct layers to our map. The first layer will be the base or background that the character will walk on. The second layer will be tiles that the player should not be able to walk on, i.e., collidable tiles. The third layer will be tiles that should always be rendered on top of the player to create a more accurate visual effect.
-
Rename the layer we just painted with grass by going to the Layers panel on the top right and double clicking the name of the layer. Call it Bottom Layer:

-
Click on the New Layer icon and call the new layer Collision Layer:

-
Make sure this new Collision Layer is selected in the Layers panel. You’ll know it’s selected because it will be highlighted in blue.
-
Paint tiles on the Collision Layer that you want the character not to be able to walk over. This could be a tree, buildings, or other objects.

It’s important that you only paint the tiles you want the character to bump into! Notice how I’m not painting the entire tree since I don’t want the character to bump into the top of the tree, only the bottom.
-
Create a new layer just as before and call it Top Layer. On this new layer, paint tiles that you want to always be rendered in front of the character.

-
Finally, save the project file (
CTRL+S/CMD+S) so that you can close the program and come back to it later.
Loading the Map
Section titled “Loading the Map”-
Click
File > Export Asand save it as./Pokémon-1/config/map.json. It’s important that the file is called exactly this and saved in this exact location. -
Start up Live Server, navigate to
Pokémon-1and you should see your map loaded in and your character should interact with the world properly:
-
Awesome! Lastly, there is functionality built into only this specific update that allows us to toggle each layer. Hopefully, this will illustrate how/when each layer is being drawn, and how the character fits in between the layers:
- Press
1to toggle the bottom layer. - Press
2to toggle the bottom layer. - Press
3to toggle the bottom layer.
- Press
Let’s look at the code to see how we’re implementing the collision detection!
Important Code
Section titled “Important Code”-
./services/Layer.js: We moved most of what was inMap.jspreviously into this file. EachLayerobject will now contain tile information about that specific layer whichMapwill draw.Layer::generateTiles(layerData, sprites): Takes the exported data from Tiled which lives in./config/map.jsonand uses it to create newTileobjects. One thing to note is that Tiled exports data indexed from1and not0, so we have to adjust this in this function. If we get a tile with ID of-1, then we don’t instantiate a newTileobject for that location, we just assign that space tonull.
-
./states/player/PlayerWalkingState.js: Since we give a reference to theMapwhen creating aPlayer, this means we can use theLayer::getTile(x, y)function from withinPlayerWalkingState.-
PlayerWalkingState::isValidMove(): Returnstrueif the player is going to move on to a non-collidable (i.e., notnull) tile.isValidMove(x, y) {return this.collisionLayer.getTile(x, y) === null;}
-
-
./services/Map.js: We take care to render first the bottom layer, then the collision layer, then the player, then the top layer. In addition to this, inPlayer::render(), we offset the Y to be a bit higher than default. This offset combined with the Map’s rendering order ensures that the game looks the most visually accurate!
💬 Pokémon-2 (The “Textbox” Update)
Section titled “💬 Pokémon-2 (The “Textbox” Update)”In Pokémon-2, we add a user interface (UI) system to the game using a State Stack. In RPG games in general, there are usually many menus and textboxes to navigate to access things like the settings, inventory, and actions during a battle. These various UI elements must be drawn on top of whatever state we’re currently in, and when the player dismisses the element, they should be able to resume exactly where the left off.
A stack is an ordered list of elements where an element is inserted at the end of the stack and is removed from the front of the stack.
Unlike a queue, which works based on the first-in, first-out (FIFO) principle, a stack works based on the last-in, first-out (LIFO) principle.
A stack has two main operations involving inserting a new element and removing an existing element. The push() operation inserts an element at the top of the stack, and the pop() operation removes an element from the top of the stack.


Image from JavaScriptTutorial
State Stack
Section titled “State Stack”- The StateStack is the foundational class for this project; every other part of this program revolves around it.
- Previously, this had been the role of the State Machine. However, while a State Machine allows us to work with one state at a time, a StateStack will allow us to render multiple states at once.
- For example, if you envision our states as a stack, you can imagine having the
PlayStatein the bottom of the stack as our player walks around, when suddenly aDialogueStateis pushed onto the stack. - Rather than having to transition from
PlayStatetoDialogueState(as we would’ve had to with a State Machine), we can simply “pause” thePlayStateand render theDialogueStateon top of it. - This allows us to return back to previous states as they were when we left them, rather than create new ones.
- Only the top-most state on the stack is being updated, though this could be changed if we wanted it to.
- For example, if you envision our states as a stack, you can imagine having the
In this example, a DialogueState which contains a Textbox is pushed onto the stack by hitting the enter key. As more and more states are added to the stack, we can see the stack visually grow on the left. When the escape key is hit, the textbox is dismissed and the state is popped off the stack.

Notice how every state is still being rendered, but only the topmost state is the one listening for updates. This is why when the escape key is hit, only the top textbox disappears and not all of them at once. Try this out and try to move the character when there is a textbox on the screen!
Class Diagram
Section titled “Class Diagram”The states will have their own UI elements as needed. Taking the example from the previous section, DialogueState would have an instance of a Textbox UI element. Here are the UI elements we’ll be using for our Pokémon game:
Important Code
Section titled “Important Code”-
./lib/StateStack.js:constructor(): Initializes a states array which will represent the stack.update(): Callsupdate()on the topmost state in the stack.render(): Callsrender()on all states in the stack.push(): Adds a new state to the stack and calls the newly-added state’senter()method.pop(): Removes and returns the topmost state from the stack and calls itsexit()method.top(): Returns the topmost state from the stack.clear(): Sets the stack array to empty.
Before we add new states to the stack, we should create some UI elements first.
./user-interface/UserInterfaceElement.js: The base class all UI elements will inherit from sincepositionanddimensionsare a common attribute among all UI elements../user-interface/elements/Panel.js: Two rectangles on top of each other with slightly different sizes and colors../user-interface/elements/Selection.js: A list ofitemswhich are objects that each have a stringtextand functiononSelectproperty. The player can navigate this list with the arrow keys and select an option with enter or spacebar../user-interface/elements/Menu.js: APanelwith aSelectionlaid overtop../user-interface/elements/Textbox.js: APanelwith text laid overtop. The complicated part is taking the string of text you want to display and separating it out into “pages” that fit within the panel’s dimensions.Textbox::getLines(text, maxWidth): Splits up the giventextinto lines based on themaxWidthof the panel.Textbox::createPages(): Takes the lines fromgetLines()and groups them into pages based on the height of the panel.Textbox::next(): When the player hits enter or spacebar, advance to the next page of text.
./states/game/DialogueState.js: Displays aTextboxwhich the player can view and advance themselves.
🐲 Pokémon-3 (The “Monster” Update)
Section titled “🐲 Pokémon-3 (The “Monster” Update)”In Pokémon-3, we implement, you guessed it, Pokémon! A Pokémon is really just a box full of various numbers that represent different aspects of the Pokémon. We’ll be sticking to the basics for our game by implementing health, attack, defense, and speed.
This is by no means necessary, but if you’re curious as to how a Pokémon’s stats get calculated, you can read up about it here.

- Pokémon are created with random Individual Values (IVs) which are like the Pokémon’s “genetic material”. This is how two Pokémon of the same level and species might have differing stats - perhaps one has higher health, while the other has higher speed.
- Every Pokémon also has a set of base stats that they start off with at level 1. These base stats are the same for every single instance of the same Pokémon. Meaning, any 2 Bulbasaur will always have the same base stats.
- Using the specific Pokémon’s IVs in conjunction with their base stats, the actual stats of the Pokémon are calculated using mathematical formulae. These formulae differ from game to game, but the core concept remains the same.
Important Code
Section titled “Important Code”./config/pokemon.json: A collection of names and stats, which makes it trivial for a non-programmer to create additional Pokémon and help out in the overall design of the game../src/services/PokemonFactory.js: The configuration data is loaded into the factory such that we can use thecreateInstance()method to spit out any Pokémon we want../src/enums/PokemonName.js: To reference the Pokémon by something sturdier than just a string. You’ll see that it’s empty; we’re actually dynamically creating these enums insidePokemonFactory::load()../src/entities/Pokemon.js: The box of numbers that constitutes a Pokémon. Honestly, look inside, they really are just a bunch of numbers! Later they’ll have couple of sprites associated with them but that’s about it. It’s remarkable they’ve been able to create one of the most successful game franchises around this!./src/user-interface/PokemonStatsPanel.js: Extends thePanelUI element to display the stats of the Pokémon../src/states/game/PokemonStatsState.js: Instantiates aPokemonStatsPanelto display to the player../src/entities/Player::initializeParty(): Uses thePokemonFactoryto populate the player’s party with one Pokémon.
🥊 Pokémon-4 (The “Battle” Update)
Section titled “🥊 Pokémon-4 (The “Battle” Update)”In Pokémon-4, we set up everything needed for our cute and cuddly boxes of numbers to battle. The Pokémon won’t be able to inflict damage to each other; that will be in the next update. There’s already lots we need to add to this update without the Pokémon actually fighting yet!
Callback Functions
Section titled “Callback Functions”By this point, you should be comfortable with the callback function pattern. We’re going to be using a LOT of callbacks from here on out, so if you need a refresher, there are many videos you can watch!
In brief, a callback function is a way to enforce an order of operations when dealing with asynchronous code.

Image from 4PSA
Many of our states will be written in this way so that we can support asynchronous behavior. For example, if we want something to happen after a dialogue screen is closed, but we don’t know when the user will close it, we can just include the desired functionality in a callback function which will be executed once the user finally closes the dialogue screen.
Important Code
Section titled “Important Code”./src/states/game/BattleState.js:- As expected, sets up our battle. We set up the player, the dialog screen, the opponent, the health indicators, setting flags along the way to ensure nothing is rendered out of turn.
- Mainly depends on
triggerBattleStart()to kick off the battle, tweening in the components of the battle screen and subsequently triggering the dialogue viatriggerStartingDialogue(), which displays the dialogue and eventually pushes theBattleMenuStateto theStateStack.
./src/states/game/BattleMenuState.js:- This is where we present the
Selectionmenu to the player and define what happens when the player chooses FIGHT or STATUS. - Most of the logic in this file is in the constructor, in which we create the menu and define the callback functions for each selection.
- When the player selects FIGHT or STATUS, we push a
BattleMessageStateto theStateStack. Once that state is popped automatically after 2 seconds, we come back to this menu state to rinse and repeat. The actual fighting logic will be implemented in the next update.
- This is where we present the
./src/states/game/BattleMessageState.js: Displays an in-combat message to the player to describe the last action that occurred. These states can either be popped automatically after a given period of time or dismissed by the player by hitting enter../src/states/game/BattlePanel.js: Two instances of these objects represent the panels in the battle screen that display each Pokémon’s name, level, and health.config/assets.json: We’ve added the Pokémon sprites to be displayed in battle.Pokemon.jshas also been updated to hold instances of these sprites../src/states/player/PlayerWalkingState.js:PlayerWalkingState::ENCOUNTER_CHANCE: Used to determine if we go into battle or not when walking over grass tiles.PlayerWalkingState::checkForEncounter(x, y): Checks if we’re currently walking on a grass tile, and if so, randomly starts a battle 10% of the time.PlayerWalkingState::startEncounter(x, y): Pushes a new instance ofBattleStateonto the stack. We pass the player instance to the newBattleStateas well as a new instance ofOpponentwhich is just a stripped down version of thePlayerclass.

🃏 Pokémon-5 (The “Turn” Update)
Section titled “🃏 Pokémon-5 (The “Turn” Update)”In Pokémon-5, we force our cute cuddly boxes of numbers to damage each other until one of them dies faints. When you put it that way, it’s strange that this game is marketed towards children!
Important Code
Section titled “Important Code”There’s only one new file for this update: ./src/states/game/BattleTurnState.js, and it is instantiated by BattleMenuState when the player chooses FIGHT!

BattleTurnState::constructor():- Stores the current
battleStateand determines which Pokémon should attack first based on speed. If the speed of both Pokémon are the same, then it falls back to a coin flip.
- Stores the current
BattleTurnState::enter():- Calls the attack function for each Pokémon and checks for battle-end conditions after each attack.
BattleTurnState::attack():- First pushes a
BattleMessageStateto let the player know who is attacking, then plays the attack animation.
- First pushes a
BattleTurnState::inflictDamage():- After the animation is complete, the damaged Pokémon is made to blink a few times, and their health is decreased upon damage calculation.
BattleTurnState::checkBattleEnded():- Checks whether a Pokémon’s health has been depleated, and if so, causes them to faint.
BattleTurnState::processDefeat():- Drops the player’s sprite from the screen, and pushes a
BattleMessageStateto let the player know they’ve fainted. Then, it pops twice to go back to thePlayState.
- Drops the player’s sprite from the screen, and pushes a
BattleTurnState::processVictory():- Drops the enemy sprite from the screen and pushes a
BattleMessageStateto let the player know they’ve won. Then, it pops twice to back to thePlayState.
- Drops the enemy sprite from the screen and pushes a
PlayState::healParty():- If the player lost the last battle, their Pokémon will be healed to full health. A
DialogueStatewill be pushed to let the player know their Pokémon has been healed.
- If the player lost the last battle, their Pokémon will be healed to full health. A

⬆️ Pokémon-6 (The “Level” Update)
Section titled “⬆️ Pokémon-6 (The “Level” Update)”In Pokémon-6, we implement a level up system whereby Pokémon can earn experience points when they win battles. If enough experience points are earned, the Pokémon will level up which means all their stats get a boost to make them stronger.
Experience
Section titled “Experience”In the core series games, experience is normally gained by all Pokémon who have been sent out against an opponent’s Pokémon, divided evenly among them. Experience is gained upon the opponent Pokémon fainting, and its amount is calculated as a function of the fainted Pokémon’s level, as well as species. Certain items can affect the distribution and amount of experience gained, as can other conditions, such as whether or not the Pokémon was caught by another person or in another language of the game.
Our implementation will be a simplified version of the above since we don’t have items or various other conditions. All Pokémon in our game will use the medium fast leveling formula to calculate how much experience is needs to level up.

Important Code
Section titled “Important Code”There are a few new additions to the Pokemon class to implement an experience system.
Pokemon::experienceFromLevel(level): Uses the medium fast leveling formula to determine how much experience is required to reach the providedlevel.Pokemon::constructor(): Three new fields have been added for experience:levelExperience: How much experience it took to get to the Pokémon’s current level. This is used to calculate the experience delta that the experience gauge displays during battle.- Example: It takes
experienceFromLevel(3) === 27experience to get a Pokémon to level 3.
- Example: It takes
currentExperience: How much experience the Pokémon currently has.- Example: A level 3 Pokémon has at least
experienceFromLevel(3) === 27experience. If they then defeat a Pokémon in battle and gain 10 experience, they will have a total of 37 current experience.
- Example: A level 3 Pokémon has at least
targetExperience: How much experience the Pokémon needs to level up.- Example: A level 3 Pokémon needs
experienceFromLevel(3 + 1) === 64experience to level up.
- Example: A level 3 Pokémon needs
Pokemon::levelUp(): Increments the Pokémon’s level, calculates newlevelExperienceandtargetExperiencevalues, and increases the stats of the Pokémon.Pokemon::calculateExperienceToAward(opponent): Once an opponent Pokémon is defeated, this function calculates the amount of experience the player Pokémon will receive based on this simplified gain formula.Pokemon::getExperienceMeter(): This is where we usetargetExperience,levelExperience, andcurrentExperienceto display the proper delta to the player so that they know now much experience their Pokémon needs to level up.
BattleTurnState now has two additional functions to handle experience points after a victory in battle.
BattleTurnState::processExperience():- Determines the amount of experience to award to the winning Pokémon and displays that information to the player in a new
BattleMessageState.
- Determines the amount of experience to award to the winning Pokémon and displays that information to the player in a new
BattleTurnState::processLevelUp(experience):- After the message is displayed, we check if it was enough experience to level up. If so, we call
Pokemon::levelUp()which will handle calculating all the boosted stats for us. - Lastly, we display a
BattleMessageStateto the player to let them know their Pokémon leveled up.
- After the message is displayed, we check if it was enough experience to level up. If so, we call
Finally, BattlePanel is now split into Battle/PlayerPanel and Battle/OpponentPanel since now we have to display different information in the BattleState depending on the player’s Pokémon and the opponent’s Pokémon.
✨ Pokémon-7 (The “Polish” Update)
Section titled “✨ Pokémon-7 (The “Polish” Update)”In Pokémon-7, we add sounds, additional sprites, a TitleScreenState, and a TransitionState that helps us smoothly fade from one state to the next.
Important Code
Section titled “Important Code”- Everyone should be comfortable with how to add sounds and music to our games since we’ve done it from day 1! Check out
./assets/sounds,./config/assets.json, and./src/enums/SoundName.jsfor the definitions. - The battle screen now has a background and platforms which can be found in
./assets/images. ./src/user-interface/PokemonStatsPanel.js: Now has an icon animation whose speed is determined by the Pokémon’s current health to convey “liveliness”../src/states/TransitionState.js: Uses tweens to fade in/out a white rectangle on the screen. We can pass in a callback that contains an instantiation for a new state if we want to fade into a new state. By default, the callback is a pop, meaning the fade will result in the previous state we were in.- We call this state when entering a battle in
PlayerWalkingState::startEncounter(). - We call this state when exiting a battle in
BattleTurnState::exitBattle().
- We call this state when entering a battle in
./src/states/TitleScreenState.js:- Consists of some text fields and a carousel of sprites that are displayed on the screen. There is then a fading transition to the next screen.
- Monitors whether the user has pressed the “enter” key, and if so, calls
TransitionState.fade()which will pop the currentTitleScreenStateand push thePlayStatehalfway into the fade. - On entering
PlayState, a newDialogueStateis pushed, so that rather than jumping head first into the game, the user can read some instructions.

And with that, we have a fully functioning game of Pokémon!