HTML 5 Shoot 'em Up in an Afternoon
Table of Contents
- Morning: Preparing for the Afternoon
- Afternoon 0: Overview of the Starting Code
- Afternoon 1: Sprites, the Game Loop, and Basic Physics
- Afternoon 2: Player Actions
- Afternoon 3: Object Groups
- Intermission: Refactoring
- Afternoon 4: Health, Score, and Win/Lose Conditions
- Afternoon 5: Expanding the Game
- Afternoon 6: Wrapping Up
- Evening: What Next?
- Appendix A: Environment Setup Tutorials
- Appendix B: Expected Code Per Chapter
I’ll be honest and get this out as early as possible: I’m not a “professional” game developer. Looking at my other Leanpub books will tell you that I’m more into web development. Heck, if you told me a few months ago that I would be putting out a game development book, I would’ve thought you’re crazy.
This book was a result of three things that happened to occur around the same time:
First was the problem that came up with our HTML5 workshop. The original lecturer bailed at the last minute and we had problems with finding a replacement. We even considered the worst case, cutting out the hands-on portion leaving us with a morning “workshop” consisting only of talks from people in the local gaming industry.
Coincidentally, I was playing around with Phaser a few weeks before the event. While I am not a game developer, I had just enough knowledge to make a simple workshop to introduce basic game concepts via the said HTML5 game framework. In the end I volunteered to take over the workshop less than four days before the actual event.
Normally I would have prepared a hundred or so slides and go through them during the workshop. But earlier that week I had the rare opportunity to talk to the first person who gave me advice when I started out teaching, and one of the things we talked about the not-so-recent trend of lazy college professors making only slides leaving a big gap between them and textbooks. This convinced me to switch things up with the workshop – instead of giving the participants a link to SpeakerDeck, I would point them to Leanpub.
It took a few sleepless nights to write the original 36-page workbook, but it was worth it: I had a much easier time conducting the workshop than I would have if I went with slides.
The positive response from the participants also convinced me to spend some more time to improve this book and get it out there for anyone interested in learning the basics of game development.
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA.
You can find the complete source of this book at https://github.com/bryanbibat/html5shootemupinanafternoon.
Phaser © 2013-2015 Photon Storm Ltd.
Art assets derived from SpriteLib, © 2002 Ari Feldman.
Sound assets © 2012 - 2013 dklon (Devin Watson).
This is usually the part where books give a lengthy intro about HTML5 to increase their word count. This is not one of those books.
All you need to know about HTML5 is that it allows you to do stuff in your browser, regardless if it’s on a desktop PC or a mobile phone, without the need for extra plugins. And that includes making games. If you want a better intro to HTML5, head over to Dive Into HTML5.
As the title and cover of the book implies, we will introduce you to both HTML5 and game development by guiding you in making a shoot-em-up game similar to the classic video game 1942.
There are a number of HTML5 libraries and frameworks out there right now. For this afternoon workshop, we’ll be using Phaser, an open-source framework built on top of Pixi.js. It’s a higher-level framework: it’s bigger and may feel like you have much less control (i.e. magical) compared to other frameworks, but at the same time, you need far less code to get things done and this makes it suitable for a short workshop such as this one.
Who is this book for?
This book is for people who want to learn the basic concepts behind creating games. As a workshop manual, it is also for experienced developers interested in introducing those concepts to those people. With these in mind, here are some possible setups for using HTML5 Shoot ‘em Up in and Afternoon:
- Pair or Small-group Study - Spend an afternoon teaching game programming to your daughter / cousin / nephew. It’s recommended to go through the book once or twice beforehand to make sure things go smoothly. (Unless of course you want to expose the kid to the reality of “spend minutes or hours looking for the copy-paste typo” software development.)
- Workshop - What this book was originally written for. Gather a group of people interested in making games in HTML5 and go through the tutorial at a slower pace. An experienced instructor (i.e. worked with Phaser for some time, gone through the tutorial multiple times) can lead a workshop of 20 without a hitch, but for larger groups or groups with less programming experience, you may need to get a few extra mentors to help.
Morning: Preparing for the Afternoon
For instructors tutoring children or other individuals with little programming or even gaming experience, we recommended spending a few hours in the morning to make sure things go smoothly in the afternoon.
If you’re the student, it’s best you skip this part so as not to spoil what your teacher is going to ask you to do.
Introduce them to Shoot ‘em Ups
It may sound weird for us who grew up in the ’80s and ’90s where shoot ‘em ups were staple arcade games, but there is a very slight possibility that the person you’re teaching may not be familiar with the genre.
If that’s the case, then you need to let them play a few shoot ‘em ups before starting the workshop. They must first understand the basic concepts around the genre, knowing what makes those games fun an challenging. At the worst case, finding out that they hate the genre will let you end the session early and spare you from an unproductive afternoon.
An obvious choice would be 1942, as it has been ported and remade so many times that you can find one on pretty much any platform.
Then there are Flash games from sites like Newgrounds and Kongregate. As HTML5 is supposed to replace Flash, letting your student play these games will give them an idea on what they can make in the future.
In addition to programming skills, students should know basic Trigonometry. Phaser already handles most of the calculation but knowing stuff like sine/cosine and polar coordinates will make it easier for them to visualize what’s going on under the hood. They will also directly use those concepts at the latter part of the workshop where we rotate sprites and generate patterns for the boss battle.
While there are many online tutorials out there for trigonometry (Khan Academy comes to mind), I have yet to see one that is better than your usual high school trigonometry class while accessible to younger students. You might even say that the other way around, introducing kids to trigonometry through game concepts, would be a better approach1.
If you still wish to quickly introduce basic trigonometry to your students before the workshop, look for visually impressive and interactive demos like How to Fold a Julia Fractal.
Development Environment Setup
You have to use a web server to test your game in this tutorial. The first part of Getting Started With Phaser explains why you should do this.
Once you have setup your web server and text editor, download the basic game template with Phaser 2.4 RC1 from Github, extract it to the folder served by the web server, and start coding.
More detailed information about setting up your development environment (like choosing a web server) can be found at Appendix A: Environment Setup Tutorials.
Other Suggested Prior Reading
Apart from JS and Math, we suggest that you at least skim through the following to give you an idea about what we are going to do:
- Getting Started with Phaser - Phaser’s own guide setting up a development environment
- Phaser Examples - view demos of Phaser’s features.
- Phaser Documentation - your typical API docs with link to source.
In addition to Phasers documentation, the following may give you insights on making games in Phaser:
- Game Programming Patterns - like many game frameworks, Phaser uses the Game Loop pattern at its core.
- Game Mechanic Explorer - a somewhat short list of game mechanics, all implemented in Phaser.
This book reached the Leanpub’s “Lifetime Number of Copies Sold” bestsellers list around December 2014. As my holiday gift of thanks to those that bought and downloaded it, I recorded a quick and dirty video walkthrough of the main chapters of the book. If you prefer watching the programming lessons in HD video (even when they are taught by a slightly drunk non-native English speaking guy), you’re in luck.
If you purchased or downloaded the book, you should be able to download the videos (all 600MB+ of them) via the “Extras” zip link on your Leanpub dashboard. If you’re reading this online or want to watch in lower resolution, you can also watch the videos on YouTube.
- True story: I discovered sine and cosine as way to make things spin or bob up and down back when I was a kid playing around with BASIC, two years before I had trigonometry class.↩
Afternoon 0: Overview of the Starting Code
By now you should have finished setting up your development environment, with your web server up and your editor open to the folder containing the base code for the tutorial. If not, please refer again to the Development Environment Setup in previous chapter.
Before we proceed to the actual tutorial, let’s take a tour of the starter template:
This template is based on the Basic Game Template found in the
resources/Project Templates folder of the Phaser Git repository. We’re using this because it follows a more modular approach compared to most of the Phaser Examples and therefore much closer to real-life apps.
Let’s do a quick run-through of the files:
index.html- our main HTML5 page that links all our files together. There’s not much to say about this except for the
<div id="gameContainer"></div>which Phaser will use to draw the Canvas on to.
phaser-arcade-physics.min.js- Phaser stripped of 2 other physics engines (retaining only “Arcade” physics) and minified. You can replace this later on with the full version if you plan to use the other physics engines or if you want to the refer to the original code while developing.
app.js- the code that kicks off the app. Creates the
Phaser.Gameobject and adds the
game.js- the different states of our game, combined together by
app.jsto form the flow of our app:
- Boot - The initial state. Sets up additional settings for the game. Also pre-loads the image for the pre-loader progress bar before passing the game to Preloader.
- Preloader - Loads all assets before the actual game. Once that’s done, the game proceeds to MainMenu.
- MainMenu - The title screen and main menu before the actual game.
- Game - The actual game.
Reading through the JS files and the comments within will give you a peek of what to expect from Phaser.
The template also includes all the necessary sprites and sounds for the basic game, saving you hours of looking for or making your own game assets. The sprites were taken from Ari Feldman’s open-sourced sprite compilation SpriteLib while the sounds were from Devin Watson’s OpenGameArt.org portfolio.
With the code tour out of the way, we can now move on to the tutorial.
Afternoon 1: Sprites, the Game Loop, and Basic Physics
In this first part, we’ll go over how to draw and move objects on our game.
Draw Bullet Sprite
Let’s start with something basic – drawing an object on the game stage. The most basic object in Phaser is the Sprite. So for our first piece of code, let’s load then draw a bullet sprite on our game by making the following modifications to
game.js. (All of the code examples in this tutorial refer to
game.js unless otherwise noted.)
We called the following functions:
load.image()- loads an image (e.g.
assets/bullet.png) and assigns it a name (e.g.
bullet) which we use later.
add.sprite()- accepts the x-y coordinates of our sprite and the name of the sprite which we assigned in the
Screen Coordinates vs Cartesian Coordinates
At around middle school, children learn about the Cartesian coordinate system where points, defined by an ordered pair
(x, y), can be plotted on a plane. The center is
(0, 0), x-values increase as you go right, while y-values increase as you go up.
However, computer displays do not use Cartesian coordinates as is but instead use a variation: instead of being at the center,
(0, 0) represents the point at the top-left, and instead of decreasing, y-values increase as you go down. This picture illustrates the screen coordinate system in our game at the moment:
Draw Enemy Animation
Let’s then proceed with something more complicated, an animated sprite.
We first load a sprite sheet, an image containing multiple frames, in the pre-loading function.
load.image(), we used
load.spritesheet() to load our sprite sheet. The two additional arguments are the width and height of the individual frames. Since we defined
32 for both width and height, Phaser will load the sprite sheet and divide it into individual frames like so:
Now that the sprite sheet is loaded, we can now add it into our game:
animations.add() function specified the animation: its name, followed by the sequence of frames in an array, followed by the speed of the animation (in frames per second), and a flag telling whether the animation loops or not. So in this piece of code, we defined the
fly animation that loops the first 3 frames of the green enemy sprite sheet, an animation of the propeller spinning:
Set Object Anchor
The sprites share the same x-coordinate, so by default they are left-aligned.
For games, however, most of the time we want the x-y coordinates to be the center of the sprite. We can do that in Phaser by modifying the anchor settings:
(0.5, 0.5) centers the sprite. On the other hand,
(0, 0) will mean that the x-y coordinate defines the top-left of the sprite. Similarly,
(1, 1) put the x-y at the bottom right of the sprite.
The Game Loop
The following is an oversimplified diagram on what happens when Phaser games run:
- Preload - The game starts with a pre-load section where all assets are pre-loaded. Without pre-loading, the game will stutter or hang in the middle of gameplay because it has to load assets.
- Create - After pre-loading all assets, we can now setup the initial state of the game.
- Update - At a set interval (usually 60 times per second), this function is called to update the game state. All updates to the game are done here. For example, checking if the character has collided with the enemy, spawning an enemy at a random location, moving a character to the left because the player pressed the left arrow key, etc.
- Render - coming after Update, here is where the latest state of the game is drawn (rendered) to the screen.
The update-render loop is what’s called the Game Loop, and is the heart of almost every computer game. You can read more about the Game Loop at the Game Programming Patterns site.
Move Bullet via update()
Now that we know how the game loop is implemented in Phaser, let’s move our bullet sprite vertically by reducing its y-coordinate in the
As mentioned above, Phaser will call the
update() function at a regular interval, effectively moving the bullet upwards at a rate of around 60 pixels per second.
This is how you move sprites in most basic game libraries/frameworks. In Phaser, though, we can let the physics engine do almost all of the dirty work for us.
Phaser comes with 2 physics systems, Arcade and P2. Arcade is the default and the simplest, and so we’ll use that.
(And besides, the version of Phaser bundled with the basic template,
phaser-arcarde-physics.min.js, contains only Arcade physics to reduce download file size.)
Once we put our bullet into the Arcade physics system, we can now set its velocity and let the system handle all the other calculations (e.g. future position).
With the physics enabled and velocity set, our sprite’s coordinates will now be updated by the
this.physics.update(); call rather than our
update code. In this case, “
velocity.y = -500” is 500 pixels per second upward; at 60 frames per second, each
update call will move the bullet up 8-9 pixels.
Show Body Debug
Arcade physics is limited to axis-aligned bounding box (AABB) collision checking only. In simpler terms, all objects under Arcade are rectangles.
We can view these rectangles by rendering these areas with the debugger. First we add the enemy sprite to the physics system:
Then we add the debugging code under our currently nonexistent
Once added to the physics system, checking collision and overlapping is only a matter of calling the right functions:
overlap() function requires a callback which will be called in case the objects overlap. Here’s the
Being common situation in games, Phaser provides us with a
sprite.kill() function for “killing” sprites. Calling this function both marks the sprite as dead and invisible, effectively removing the sprite from the game.
Here’s the collision in action:
With debug on, we can see that the sprite is still at that location but it’s invisible and the physics engine ignores it (i.e. it no longer moves).
Debugging isn’t really required in this workshop so you should probably remove or comment out the debugging code when you’re done testing.
Before we proceed to the next lesson, let’s improve our collision handling by adding an explosion animation in the place of the enemy. Here’s the animation pre-loading:
Then the actual explosion:
Here we used a different way to setup animations. This time we used
animations.add() with only the name of the animation. Lacking the other arguments, the
boom animation uses all frames of the sprite sheet, runs at 60 fps, and doesn’t loop.
We want to tweak the settings of this animation, so we add them to the
explosion.play() call as additional arguments:
15- set the frames per second
false- don’t loop the animation
true- kill the sprite at the end of the animation
The last argument the most convenient to us; without it we’ll need to register an event handler callback to perform the sprite killing, and event handling is a much later lesson. In the meantime, enjoy your improved “shooting down an enemy” animation:
Afternoon 2: Player Actions
Now that we’re done with drawing and movement, let’s move on to making an object that will represent us in the game. Load the player sprite in the
Add the following to the
create() function before the enemy sprite to add our sprite into the game:
Implementing keyboard-based input is straightforward in Phaser. Here we begin by using a convenience function which returns the four arrow keys.
Let’s also set the player’s initial speed speed as a property of the player object on create since we’ll be using this value multiple times throughout our program:
This will also allow us to have planes with different speeds or “speed up” type of power-ups later.
Once that’s done, we can now set the velocity like so:
Note that we set the velocity to zero so that the plane stops when the input stops. We also allow the player to input both vertical and horizontal movement at the same time.
Arcade physics also makes it easy to make the edges of the stage act like walls:
Point-based movement usually requires hand-rolling your mathematical calculations. Fortunately, Phaser already has functions which calculates the angle and velocity based on input points.
Here’s how simple it is to move an object towards the pointer:
Based on the object’s location and a speed, the Arcade physics function
moveToPointer() calculates the angle and velocities required to move towards the pointer at the input speed. Calling this function will already modify the x and y velocities of the object, which is exactly what we need in this situation.
This function will not rotate the sprite, though, so if you need to rotate the sprite accordingly, you can use the return value of the function which is the angle of rotation in radians. We shall see an example of this in a later lesson.
Just a word of warning, the movement in a frame may overshoot the target (i.e. move 5 pixels even though the pointer is 2 pixels away) causing your player sprite to tremble instead of staying put. The inaccurate coordinates given by a touch screen may also produce a similar effect. A crude way of getting over these is to stop movement at a certain distance from the pressed point, like so:
If you need more precise input, you may be better off implementing an on-screen directional pad.
Let’s remove our old bullet code and add new code for creating bullets on the fly.
We set our fire button to Z or tapping/clicking the screen:
Then we create a new function that will fire a bullet just above the nose of player’s sprite:
And finally we modify our collision detection code to iterate over the bullets:
One obvious problem that you’ll see as you test this new firing code is that the bullets come out at a very high rate. We can throttle this by storing a time value specifying the earliest time when the next bullet can be fired.
Add the variable
shotDelay (set to 100 milliseconds) to the
Then modify the
fire() function to check and eventually set the
If you haven’t noticed it yet, the other problem with our current bullet generation approach is it’s essentially a memory leak. In the next chapter, we’ll discuss one way of limiting the resources that our game will use.
Afternoon 3: Object Groups
Instead of creating objects on the fly, we can create Groups where we can use and re-use sprites over and over again.
Convert Bullets to Sprite Group
Bullets are best use case for groups in our game; they’re constantly being generated and removed from play. Having a pool of available bullets will save our game time and memory.
Let’s begin by switching out our array with a sprite group. The comments below explain our new code.
Let’s move on to the
Here we replaced creating bullets on the fly with reviving dead bullets in our pool.
Update collision detection
Switching from array to group means we need to modify our collision checking code. Good news is that
overlap() supports Group to Sprite collision checking.
There is a minor quirk when comparing “Groups to Sprites” (see if you can notice it) that is not present in “Sprite to Groups” or “Group to Groups”. This shouldn’t be a problem since we’re only doing the latter two after this section.
Enemy Sprite Group
Our game would be boring if we only had one enemy. Let’s make a sprite group so that we can generate a bunch more enemies so that they can start giving us a challenge:
And again, modifying the collision code become Group to Group:
Randomize Enemy Spawn
Many games have enemies show up at scripted positions. We don’t have time for that so we’ll just randomize the spawning locations.
Add this to the
bulletPool, we also store the next time an enemy should spawn.
Note that we did not use
Math.random() to set the random enemy spawn location and speed but instead used the built-in randomizing functions. Either way is fine, but we chose the built in random number generator because it has some additional features that may be useful later (e.g. seeds).
Let’s further increase the challenge by allowing our plane to blow up.
Let’s first add the collision detection code:
Then the callback:
You might notice that even though the plane blows up when we crash to another plane, we can still fire our guns. Let’s fix that by checking the
Another possible issue is that our hitbox is too big because of our sprite. Let’s lower our hitbox accordingly:
This hitbox is pretty small, but it’s still on par with other shoot em ups (some “bullet hell” type games even have a 1 pixel hitbox). Feel free to increase this if you want a challenge.
Use the debug body function if you need to see your sprite’s actual hitbox size. Don’t forget to remove it afterwards.
Convert Explosions to Sprite Group
Our explosions are also a possible memory leak. Let’s fix that and also do a bit of refactoring in the process.
Put this on the
create() after all of the other sprites:
Then create a new function:
And refactor the collision callbacks:
Before we proceed with the rest of the lessons, let’s refactor the code to make it easier for us to change and maintain the code later. This should not change the behavior of the game, so this is just an intermission rather than a full afternoon chapter.
First on our list of things to refactor are our
update() functions. They’re getting bigger and they will be worse as we proceed with the workshop. We’ll refactor them by splitting these large functions into smaller functions.
Let’s start by extracting functions out of
create(). Replace the contents of the function with:
Then insert the following after
We also added a call to
this.sea.autoScroll() so that we can remove the
this.sea.tilePosition.y += 0.2 from the
Now replace the contents of
update() with the following:
Insert the new functions after the
Reducing Hard-coded Values
Apart from long functions, our game also has many hard-coded values and this may affect code readability and maintenance later.
Eliminating all hard-coded values would be overkill especially for a tutorial like this, so our goal here would be show the ways how we could reduce them.
Using Relative Values
A good portion of the hard-coded values are x-y coordinates. Replacing them with values relative to
game.height will allow us to change the size of the game later with minimal impact to the code.
Let’s start with the background tile sprite:
Then we change the player starting location to the bottom middle of the screen:
Also the instruction text:
And finally the spawn location for the enemies:
One advantage of using relative values is that we can change the dimensions of the game without having to change any of the code. For example, here’s the game with the height and width flipped at
We can also replace many hard-coded values with constants. If you open
boot.js, you’ll see that all of the constants that we need for this workshop are already defined under the
BasicGame object. All we need to do is to replace the existing code with their respective constants:
Afternoon 4: Health, Score, and Win/Lose Conditions
Our game looks more like a real game now, but there’s still a lot of room for improvement.
Phaser makes modifying enemy toughness easy for us because it supports health and damage calculation.
Before we could implement health to our enemies, let’s first add a
hit animation (finally using the last frame of the sprite sheet):
The new animation is a very short non-looping blinking animation which goes back to the original
fly animation once it ends.
Let’s now add the health. Sprites in Phaser have a default health value of 1 but we can override it anytime:
We could have used
enemy.health = BasicGame.ENEMY_HEALTH but
reset() already has an optional parameter that does the same.
And finally, let’s create a new function to process the damage, centralizing the killing and explosion animation:
kill()s the sprite once its health is reduced to zero.
We don’t need to explain how important it is to display the player’s current score on the screen. Everyone just knows it.
First set the score rewarded on kill:
We used the full form of the
setAll() function. The last four parameters are default, and we only change the last parameter to
true which forces the function to set the
reward property even though it isn’t there.
Next step is to add the
setupText() code for displaying the starting score:
And then let’s add it to our enemy damage/death handler:
Sudden death games are cool, but may be “unfun” for others. Most people are used to having lives and retries in their games.
First, let’s create a new sprite group representing our lives at the top right corner of the screen.
For the life icons, we just used the player’s sprite and scaled it down to half its size by modifying the
With the life tracking done, let’s add the blinking ghost animation on player death:
Then let’s modify
playerHit() to activate “ghost mode” for 3 seconds and ignore everything around us while we’re a ghost:
And finally, we modify the
processDelayedEffects() function to check if the ghost mode has already expired:
Win/Lose Conditions, Go back to Menu
One of the last things we need to implement is a game ending condition. Currently, our player can die, but there’s no explicit message whether the game is over or not. On the other hand, we also don’t have a “win” condition.
Let’s implement both to wrap up our prototype.
Create a new function to display the end game message:
playerHit() function to call the “Game Over!” message:
Do the same to the
addToScore() function, but now to destroy all enemies (preventing accidental death and also stopping them from spawning) and display “You Win!!!” message upon reaching 2000 points:
(No need to set 2000 as a constant because it’s only a temporary placeholder. We’ll change this value in the last afternoon chapter.)
Let’s also display a “back to main menu” message a few seconds after the game ends. In
Since our main menu button is the same action as firing bullets, we can modify
processPlayerInput() function to allow us to quit the game:
Before going back to the main menu, let’s destroy all objects in the world to allow us to play over and over again:
Going back to the main menu will display a black screen with text. This is because we skipped loading the title page image in
preloader.js. To properly display the main menu, let’s temporarily add the pre-loading in
Enjoy playing your prototype game!
Afternoon 5: Expanding the Game
Let’s flesh out the game by adding an additional enemy, a power-up, a boss battle, and sounds.
The green enemy fighters in our current game pose no real threat to our players. To make our game more difficult, our next enemy type will be able to shoot at our player while also being faster and tougher.
First load the sprite sheet in the pre-loader:
Then create the group for the sprites (which we will call “shooters” from now on):
Instead of moving only downwards like the regular enemy, we’ll make the shooters move diagonally across the screen. Add the following code into
The figure above shows the initial spawn and target areas for the shooters; the arrows show possible flight paths. Here we’re using
moveToXY(), a function similar to
moveToPointer() which moves the object to a given point in the world.
moveToXY() returns the angle towards the target in radians, and we can assign this value to
object.rotation to rotate our sprite towards the target. But applying the value directly will result in incorrectly oriented shooters:
This is because Phaser assumes that your sprites are oriented to the right. We rotated our sprite counterclockwise
Math.PI / 2 radians (90 degrees) to compensate for the fact that our sprite is oriented downwards.
Angles/Rotation in Phaser
Angles in Phaser are same as in Trigonometry, though it might look wrong at first glance for those used to Cartesian coordinates rather than screen coordinates.
The rotation seems flipped (increasing angles are clockwise rotations rather than counterclockwise) because the
y values for the two coordinate systems are flipped.
By the way, you can use
object.angle instead of
object.rotation if you prefer rotating in degrees rather than radians.
Setting up the bullets are pretty much the same as the regular bullets. First the
Then the sprite group at
We’ve already set the shot timer for the individual shooters in the spawning section. All that’s left is to create a new function that fires the enemy bullets.
And the actual function, iterating over the live shooters in the world:
To wrap things up, let’s handle the collisions for the shooters as well as their bullets:
We’ll also destroy the shooters and bullets in
addToScore() upon winning:
Our regular bullet stream is now a lot weaker with the introduction of the shooters. To counter this, let’s add a power-up that our players can pickup to get a spread shot.
Pre-loading the asset:
Then creating the sprite group:
We also add the possibility of spawning a power-up when an enemy dies, 30% chance for regular enemies and 50% for shooters:
Add the call in
damageEnemy() to a function that spawns power-ups:
Here’s the new function for spawning power-ups:
You might have noticed the
this.weaponLevel == 5 in the last code snippet. Our weapon strength will have up to 5 levels, each incremented by picking up a power-up.
Setting the initial value to zero:
Adding a collision handler:
And a new function for incrementing the weapon level:
A common theme in shoot ‘em ups is that your weapon power resets when you die. Let’s add that into our code:
And finally, the code for implementing the spread shot:
One last thing before you test your new spread shot: let’s increase the win condition to 20,000 points so that the game will not end before you can see your new weapon in all its greatness:
Note that it’s you can run out of available bullet sprites as shown with the bullet gaps above. You can avoid this by increasing the amount of bullet sprites created in the
setupBullets() function, but it’s not really that necessary gameplay-wise.
Shooters are nice, but our game wouldn’t be a proper shoot ‘em up if it didn’t have a boss battle.
First let’s setup the sprite sheet pre-loading:
We made a group containing our single boss. This is for two reasons: to put the boss in the proper sprite order - above the enemies, but below the bullets and text; and to step around the sprite vs sprite collision coding quirk we mentioned way back. We also stored the actual boss in a property for convenience.
We then replace what happens when we reach 20,000 points from ending the game to spawning the boss:
Then the new
bossApproaching flag is there to make the boss invulnerable until it reaches its target position. Let’s add the code to
processDelayedEffects() to check this:
Once it reaches the target height, it becomes a 500 health enemy and starts bouncing from right to left using the built-in physics engine.
Next is to setup the collision detection for the boss, taking into account the invulnerable phase:
And modify the
damageEnemy() to get our game winning condition back:
We’ve saved the boss shooting code for last:
There are two additional phases to this boss fight after the “approaching” phase. First is where the boss just fires 10 bullets concentrated to the player.
Then once the boss’s health goes down to 250, the boss now fires 10 bullets at the area around the player. While this is the same amount of bullets as the previous phase, the spread makes it much harder to dodge.
We’ve saved the sound effects for the end of the workshop because integrating it with the main tutorial may make it more complicated that it should be.
Anyway, adding sound effects in Phaser is as easy as adding sprites. First, pre-load the sounds:
You can use multiple formats for each loaded sound; Phaser will choose the best format based on the browser. Using Ogg Vorbis (.ogg) and AAC in MP4 (.m4a) should give you the best coverage among browsers. WAV should be avoided due to its file size, and MP3 should be avoided for public projects due to possible licensing issues.
Once loaded, we then initialize the audio, adding a new function
Then play the audio when they are needed. Enemy explosion:
Go ahead and play your game to check if the sounds are properly playing.
You might notice that the sound effects are pretty loud especially when you’re playing in a quiet room. To wrap up this chapter, let’s adjust the game’s volume. It accepts a value between 0 and 1 so let’s pick
And now we’re done with the full game. We wrap up the tutorial in the next chapter.
Afternoon 6: Wrapping Up
We need to do one last thing before we unleash our game to the public.
Restore original game flow
At the start of the tutorial, we modified our game to skip directly to the
Game state. Now that the game’s done, we’ll need restore it to its original flow that we discussed in Afternoon 0.
Let’s start by deleting the
preload() function in
Do the same for
Revert the starting state in
And before we forget, let’s destroy the sprites that we added in the previous chapter when we quit the game:
Sharing your game
The good thing about HTML5 games is that it’s no different from a typical static HTML web site: if you want to share your game to the world, all you need to do is find a web server, upload your files there, and access the server through your browser.
If you used a cloud IDE like Codio or Nitrous.IO for this tutorial, you don’t need to do anything – you can just share the preview URL you used when you developed your game. If you developed locally, however, you’ll need to decide from the thousands of web hosting solutions out there to host your game.
The only free hosting solution I can recommend right now is Github Pages. Most of the alternatives are either seedy ad-infested sites or free-tier cloud solutions (e.g. AWS, Azure) that require a bit of tinkering just to serve our game.
Unfortunately, Github Pages is not as easy as “drag-and-drop”; you still need to know Git before you can use. So for the sake of those who aren’t experienced web developers, we’ll be using the simplest free static web hosting out there that doesn’t bombard you with ads: Neocities.
Steps for deploying to Neocities
Neocities has a straightforward sign-up page and a simple drag-and-drop interface making it easy even for beginners. There are some caveats, though:
- Neocities doesn’t support folders
- Neocities doesn’t allow you to upload
Taking these into account, here are the steps to using Neocities to host your game:
Remove all audio - Remove all of the
game.js. Refer to the previous chapter to find their locations.
Update asset locations - Move all images from the assets folder to the root then update all of the references in
preloader.jsto point to the correct location i.e.
- Sign-up for Neocities - Fill up the form at https://neocities.org/new.
Overwrite index.html - Replace its contents with your
Upload the game files - Drag and drop all of the
.pngfiles as well
favicon.icoto the files box. If dragging multiple files doesn’t work, upload them one by one.
- Verify the game works by opening the site - If all goes well, you should now see your game (sans sound).
Evening: What Next?
Congratulations! You’ve just created and deployed your first HTML5 game!
Your journey is far from over, though, and in this chapter we’ll go through your next steps.
A common problem with coding workshops is that some participants think they have already grasped the concepts well when in reality they just knew how to correctly copy-paste the code examples. Prove that you’re not one of those people by taking on the following challenges:
Add bombs to the game
Players start with 3 bombs, the current count represented by icons on the top left corner of the scene. Pressing X or tapping one of these icons will trigger the bomb, continuously destroying all enemy bullets and dealing a small amount of damage for a few seconds. This is usually done the moment before an enemy bullet collides with the player.
Hint: Use the
bomb.pngas the icon and
bomb-blast.pngas the effect that will cover the whole screen colliding with all enemies.
Use the second power-up
There’s an additional power-up image in the
assetsfolder. Use it to give the player a speed boost or a different weapon. For example, here we made the red power-up give a concentrated shot which can be more effective in the boss battle:
Create a difficulty progression
Apart from the boss fight at 20000 points, the difficulty stays the same for most of the game session. Add some flags and additional checking to make the game slightly more difficult as the game progresses. For example:
|Score||Enemy Spawn Rate||Shooter Spawn Rate||Boss|
|0 - 2000||1.0s||n/a||n/a|
|2000 - 5000||0.8s||3.0s||n/a|
|5000 - 10000||0.6s||2.5s||n/a|
|10000 - 17500||0.5s||2.0s||n/a|
|17500 - 25000||0.3s||1.5s||n/a|
|> 25000||0.6s||stop spawning||spawn the boss|
Add new patterns and phases to the boss fight
The patterns can be movement patterns (not just bouncing left and right) and shooting patterns.
- Display the breakdown of kills at the end of the game
Add new enemies: the destroyer and the sub
There are two unused enemy sprite sheets: one for a destroyer and one for a submarine. Being sea units, they will have to behave a bit differently from their flying counterparts, namely, they are below all other flying sprites, and they can’t overlap with each other.
Refactor parts of the code.
There are still places where the code is duplicated 3 or more times. Turn them into functions to reduce the code size.
You can also try converting some of the game objects into JS objects. The Tank example in Phaser Examples is way to implement this.
Convert time-related events to use Phaser’s time classes
Many of the time-related code in our game only uses the current time as reference. This results in incorrect behavior in certain situations (e.g. pausing the game).
What we didn’t cover
We’ve skipped a lot of Phaser topics. Here are some topics you might want to look into after this workshop:
- Other Phaser settings (e.g. auto-scale, pause on lose focus)
- Background music
- Mobile support (e.g. additional features, packaging to app stores)
- P2 physics
- Performance tuning and Debugging
- Persisting data
- Interacting with libraries and APIs
We also did not cover how to prepare assets for your game. There are lists of free resources out there like this wiki page (which also lists where we got our sounds, OpenGameArt.org). You can also Google for assets, but you have to check their licenses and see if you can use them in your games.
Processing assets is also something that is out of the scope of this tutorial. For example, our art assets came from SpriteLib but they had to be converted into sprite sheets that are compatible with Phaser (e.g. convert blue to transparent, add damage effect to enemy, etc.), and the volume of our sound assets had to be tweaked a bit.
Appendix A: Environment Setup Tutorials
This section is divided into 3 sections. The Basic section which provides the most basic ways of setting up your development environment for Phaser, the Advanced section which are for experienced developers who want a more comfortable environment at the price of complexity, and the Cloud section where we have tutorials on how to develop without requiring anything other than a browser and a stable internet connection.
Here’s a basic step-by-step tutorial on preparing your system for the workshop:
- Download the basic game template from Github and extract it into a folder.
- Download either version 2 or beta version 3 of Sublime Text and install it in your computer.
- In Sublime Text, add the folder you extracted to the current project by using
Add Folder to Project...
- This last step, setting up a web server, will depend on your operating system:
If you’re using Windows, the smallest and easiest web server to setup is Mongoose.
If you’re using a Mac or a Linux / Unix machine, the easiest is Python’s
SimpleHTTPServersince pretty much all of these OSs have Python pre-installed.
Repeating Mongoose’s tutorial:
- Download Mongoose Free Edition and copy it into the working folder.
- Run Mongoose. Unblock the firewall for Mongoose by clicking
- Your browser should now be open at the game template.
Starting a Simple Python HTTP Server
- Open your terminal and go to your working folder.
python -m SimpleHTTPServer.
- Open your browser to http://localhost:8000/ to access your game.
Some experienced web developers might open the basic template and be disappointed at how plain it looks compared to the code they use in their day to day work. To answer this problem, I’ve made a couple of alternative templates that have the 2 features that I can’t live without when developing front-ends: LiveReload and a means for concatenating/minifying/preprocessing JS and CSS.
You can find a starting template for NodeJS at the
This template is a slightly modified version of Luke Wilde’s phaser-js-boilerplate which uses Browserify, Jade, Stylus, Lodash, JsHint, Uglify.js, Google Analytics, Image optimisation tools, LiveReload, Zip compression, and partial cache busting for assets.
grunt (which you might have to install via
npm install -g grunt-cli) to start server and open the default browser to http://localhost:3017. You can change the port settings in
grunt build to compile everything (pre-process, concatenate, minify, etc.) to the
build folder for production release.
Refer to the original boilerplate’s Github Read Me for other details.
You can find a starting template for Ruby at the
ruby branch of the base template.
This template uses Middleman for features like LiveReload and Asset Pipeline. Compared to the NodeJS template, this template’s set of libraries are more oriented towards the Ruby ecosystem: ERb and Haml instead of Jade, Sass instead of Stylus, and so on.
To start server:
Your game will be available at http://localhost:4567. Note that LiveReload is set up to work only for localhost, if you want to make it work on a different machine in the network, you must specify the host in
To compile everything to the
Refer to the Middleman docs for other details.
Note that Middleman’s Sprockets interface doesn’t support audio so
audio_path won’t work. Check out
_preloader.js.erb for my workaround.
Cloud IDE Setup
Online IDEs like Codio and Cloud9 serve as alternative to desktop/laptop-based development. They take away the hassle of having to install additional software on your computer and replace it with the hassle of finding a venue that has reliable internet - this can be a big problem for workshops.
We’ll run through the steps of setting up two types development environment: one using Codio on the basic template, and another using Nitrous.IO on the advanced Ruby template.
Codio + Basic Template
- Sign-up for Codio by filling out the form at https://codio.com/p/signup.
- At the dashboard, click “New Project”. Click the more options link (“Click here”), choose Import and enter the Git URL https://github.com/bryanbibat/html5shmup-template.git. Fill out the project name and description then click the “Create” button.
- Wait until Codio finishes creating your project. You should be able to start editing your files once that is done.
- Click the “Project Index (static)” button/link at the header to open your game in a new tab. You can also access your game by opening the URL shown in another browser tab or window.
Cloud9 + NodeJS Template
- Sign-up for Cloud9 by filling out the form at https://c9.io/web/sign-up/free.
- At the dashboard, click “Create a new workspace”. Fill out the details and the Git URL, choose the “Node.js” template, and click the “Create Box” button. Leave the Github repository blank.
- Wait until Cloud9 finishes creating your project. Once that is done, go to the bash console at the bottom and checkout the
You can also choose your preferences at the Welcome screen while the installation is in progress.
- To view our app, Cloud9 directs traffic to one port and IP address defined by the
IPenv variables respectively. Open
gruntfile.js, modify the
grunt --forceto start the app and ignore the
- Open “Preview -> Preview Running Application” to open your game in a new window.
You can also press the “Pop Out Into New Window” button to open the preview in a new tab.
You can now edit your files and make your game. Unfortunately since Cloud9 only opens one port, LiveReload will not refresh the game automatically upon saving.
Appendix B: Expected Code Per Chapter
We understand that there are cases you need to “cheat” and need to look for the “correct” code after each chapter.
Maybe you’ve been spending too much time trying to find where you mistyped the code. Maybe a participant had to leave the workshop for 1 hour to deal with an emergency.
Regardless of the reason, here are the working code for each chapter of the workshop.
- Overview of the Starting Code - Browse in Github, Download Zip
- Sprites, the Game Loop, and Basic Physics - Browse in Github, Download Zip
- Player Actions - Browse in Github, Download Zip
- Object Groups - Browse in Github, Download Zip
- Refactoring - Browse in Github, Download Zip
- Health, Score, and Win/Lose Conditions - Browse in Github, Download Zip
- Expanding the Game
- Wrapping Up
To make sure the lazy people don’t cheat all the way, we won’t provide links to solutions for the Challenges.