In this post, we’ll learn how to add a second player to our game. But not just any player…. Pikachu!! We’ll explore how to find Pokemon sprite sheets, customize them to our needs and add them to our game.
You can play a live version of the game (link) or check out the completed code as a Glitch project (link).
Getting started
Previously, we created a basic platformer game in Javascript, from scratch, as the first lesson in our Platformer course.
Today we’ll add multiplayer to our game. This tutorial builds on the first lesson, so be sure to complete the first lesson. For this lesson, we’ll use a clean copy of the platformer from lesson one. Click on the Glitch project (don’t forget to click the “Remix” button). As always, if you ever get stuck or have any questions, feel free to reach out to us!
Finding Pikachu on the Internet
One of the most enjoyable aspects of making our own game is adding whatever creatures we like. If you’re a fan of Pokemon, it turns out there are a ton of sites out there that have Pokemon sprites. For example, if we search for “pikachu sprite sheet” on pikpng.com (link), we can find this sprite sheet:
Look closely at the second row of Pikachu animations: it shows Pikachu walking. In our Glitch project, I’ve already included a cropped version of the animation in the assets
folder, so we can just use that in our game:
In a later lesson we’ll learn how to use some free tools to crop out animations for our sprites.
Adding Pikachu to our game
Now let’s load the sprite sheet into memory. In the code snippet below, see how I set the frameWidth
and frameHeight
to be the same size as a single Pikachu image in the sprite sheet (39×38 pixels):
function preload() { | |
// (I've skipped showing all the code we wrote before) | |
// Add the code below until the "stop" comment | |
this.load.spritesheet( | |
'pikachu', | |
'https://cdn.glitch.com/adbffbc8-16bf-434f-b487-0def82849e5d%2Fpikachu-spritesheet.png?v=1629776578033', | |
{ frameWidth: 39, frameHeight: 38 } | |
); | |
// stop | |
} |
Be sure to use your own URL, by clicking on the pikachu-spritesheet.png
in your assets
folder and copying the URL there.
Next, create a player2
variable in the main.js
file:
import Phaser from '/phaser.js'; | |
var player; | |
// Add the code until the "stop" comment | |
var player2; | |
// stop |
In the create()
function we’ll define the player2
variable, similar to the first player:
function create() { | |
// (I've skipped showing all the code we wrote before) | |
// Add the following code until the "stop" comment | |
player2 = this.physics.add.sprite(500, 450, 'pikachu'); | |
player2.setBounce(0.2); | |
player2.setCollideWorldBounds(true); | |
player2.isAlive = true; | |
this.physics.add.collider(player2, platforms); | |
this.anims.create({ | |
key: 'left2', | |
frames: this.anims.generateFrameNumbers('pikachu', { | |
start: 0, | |
end: 3, | |
}), | |
frameRate: 10, | |
repeat: -1, | |
}); | |
this.anims.create({ | |
key: 'right2', | |
frames: this.anims.generateFrameNumbers('pikachu', { | |
start: 0, | |
end: 3, | |
}), | |
frameRate: 10, | |
repeat: -1, | |
}); | |
// "stop" | |
} |
In lines 12–30 we create two new animations, left2
and right2
. These animations will animate Pikachu by playing frames 0 through 3.
Now let’s reload our game; it should show Pikachu!
Controlling Pikachu
Now let’s add some keys to control Pikachu. For Player One, we used the arrow (aka “cursor”) keys. For Player Two, let’s use the “WASD” keys. First declare a keys
variable near the top of the main.js
file:
import Phaser from '/phaser.js'; | |
var player; | |
var player2; | |
var cursors; | |
var stars; | |
var score = 0; | |
var scoreText; | |
var bombs; | |
// Add the code below | |
var keys; |
Now we’ll define the keys variable in the create() function:
function create() { | |
// (I've skipped showing all the code we've written before) | |
// Add this code below | |
keys = this.input.keyboard.addKeys('W, A, D'); | |
} |
In line 5 above, addKeys()
creates an object that will respond when we press any of the W, A or D keys. We’ll add code that reacts to those key presses in a moment.
But first, we’re going to refactor our code a bit. As we add more functionality to our game, we’ll want to reorganize it so that it’s clearer and more flexible for future additions; that’s called “refactoring”. In the update()
function, we’re going to move all that code into a new function updatePlayerOne()
. This way, we can later add a function updatePlayerTwo()
.
function updatePlayerOne() { | |
// This is the code you moved from the update() function | |
if (cursors.left.isDown) { | |
player.setVelocityX(-160); | |
player.anims.play('left', true); | |
} else if (cursors.right.isDown) { | |
player.setVelocityX(160); | |
player.anims.play('right', true); | |
} else { | |
player.setVelocityX(0); | |
player.anims.play('turn'); | |
} | |
if (cursors.up.isDown && player.body.touching.down) { | |
player.setVelocityY(-310); | |
} | |
} |
function update() { | |
updatePlayerOne(); | |
} |
Next, let’s create a new function updatePlayerTwo()
, and call it in the update()
function:
function update() { | |
updatePlayerOne(); | |
// Add the following line of code | |
updatePlayerTwo(); | |
} |
function updatePlayerTwo() { | |
if (keys.W.isDown) { | |
if (player2.body.touching.down) { | |
player2.setVelocityY(-310); | |
} | |
} else if (keys.A.isDown) { | |
player2.flipX = true; | |
player2.setVelocityX(-160); | |
player2.anims.play('left2', true); | |
} else if (keys.D.isDown) { | |
player2.flipX = false; | |
player2.setVelocityX(160); | |
player2.anims.play('right2', true); | |
} else { | |
player2.setVelocityX(0); | |
player2.anims.stop(); | |
} | |
} |
Similar to the controls for Player One, when we press the W key, then set Player Two’s velocity upward (-310). One interesting bit of code is line 7 and line 11, which flips our sprite. We have to flip the sprite because our Pikachu sprite sheet is only facing to the right, so, when moving left, we flip the image.
Let’s try out our new controls!
Making Pikachu collect stars and freeze to bombs
Now let’s enable Pikachu to collect stars and get frozen by bombs. This is similar logic to Player One. Take a moment to think about what game logic enabled this, then see if you got it right, below!
function create() { | |
// (I've skipped showing all the code we wrote before) | |
// Add the following code until the "stop" comment | |
this.physics.add.overlap(player2, stars, collectStar); | |
this.physics.add.collider(player2, bombs, hitBomb, null, this); | |
// stop | |
} |
In lines 5 and 6, whenever player2
(our Pikachu) collides with stars, then call the same collectStar()
function. Similarly, if player2
collides with bombs, then call the same hitBomb
function.
Now our Pikachu can collect stars and be hit by the bomb:
Bonus: teamwork!
Notice if either Pikachu or Player One touches a bomb, then it’s game over. Wouldn’t it be awesome if the other player could “unfreeze” their teammate? Kinda like freeze tag. Let’s add this logic and then wrap up our post on multiplayer.
First, we’ll need to track if our players are alive, by adding the following code at the bottom of our create()
function:
function create() { | |
// (I've skipped showing all the code we wrote before) | |
// Add the following code until the "stop" comment | |
player.isAlive = true; | |
player2.isAlive = true; | |
// stop | |
} |
Lines 5–6 define an isAlive
variable, which will track if, well, our players are alive. We’ll set them to true
at the start of our game.
Next, we’ll change our update()
function to check if our players are alive, before updating them:
function update() { | |
if (player.isAlive) { | |
updatePlayerOne(); | |
} | |
if (player2.isAlive) { | |
updatePlayerTwo(); | |
} | |
} |
Finally, we’ll replace the hitBomb()
function with the following code:
function hitBomb(pl, bomb) { | |
pl.isAlive = false; | |
pl.setTint(0xff0000); | |
pl.anims.pause(); | |
pl.setVelocityX(0); | |
if (!player.isAlive && !player2.isAlive) { | |
this.physics.pause(); | |
this.add.text(200, 100, 'game over!', { | |
fontSize: '64px', | |
fill: '#000', | |
}); | |
} | |
} |
This code is worth an explanation. First, notice we changed the first variable name to pl
. This is to prevent a “name collision” with the global variable player
. Later, we’ll talk about how to avoid creating these global variables for cleaner, more maintainable code.
Back to the code: whenever player
or player2
collides with a bomb, the hitBomb()
function is called, and the pl
variable is assigned to either player
or player2
. Lines 2–5 will “freeze” that player and “kill” the player ( pl.isAlive = false
).
Line 7 checks if both players are dead, if so, then pause the game and show the Game Over text.
Now if we run our game, if a bomb kills either player, the other player can continue to collect stars. Let’s add the final touch: if a live player touches a dead one, they’ll come back life. This is the part in freeze tag where you touch a frozen friend to “unfreeze” them.
At the bottom of the create()
function, add the following code:
function create() { | |
// (I've skipped showing all the code we wrote before) | |
// Add the following code until the "stop" comment | |
this.physics.add.collider(player, player2, playerCollide, null, this); | |
// stop | |
} |
Next, we’ll define the playerCollide()
function, which gets called whenever both players collide:
function playerCollide(pl1, pl2) { | |
var deadPlayer = pl1.isAlive ? pl2 : pl1; | |
deadPlayer.isAlive = true; | |
deadPlayer.setTint(0xffffff); | |
} |
Line 2 uses that “ternary” operator again (remember from our platformer game tutorial?). deadPlayer
will be either player or player2, depending on who is alive. Lines 4–5 will make our deadPlayer alive again, and remove the red tint.
Now if we run our game, after a player is frozen, the other player and unfreeze them by touching them. Awesome teamwork!
Conclusion
Congratulations on adding multiplayer to our game! You are well on your way to learning Javascript and writing fun games too! If you want to review the intro tutorial, check out the first post on writing your first platformer game in Javascript (link) as part of our Platformer course (link).
If you got stuck or have any questions, feel free to contact us. Happy to help debug together.
What’s next? Try adding other characters, with different animation speeds or sizes. Try adding more animations from the sprite sheet. Try adding a big boss to the game. Your imagination is the limit!!!!