Introduction
Welcome to part two of my series on learning Javascript. In this article, we'll create the classic Space Invaders game, step-by-step. For now we're keeping it really simple - no frameworks, just raw JavaScript and HTML. Here's a screenshot of what we're going to make. Click it to try it in your browser.
The Learn JavaScript Series
This is part two of my Learn JavaScript series:
Learn JavaScript Part 1 - Create a StarField
Learn JavaScript Part 2 - Space Invaders
Learn JavaScript Part 3 - AngularJS and Langton's Ant
The series is all about learning JavaScript and the HTML5 tech stack with hands on projects.
Step 1 - Folder Structure
As we're going through JavaScript development from the beginning, let's briefly talk about the folder structure for a typical webpage. Here's what we'd normally set up:
spaceinvaders
- js
- css
- img
- lib
That's pretty lean and standard. We've got a 'js' folder for our JavaScript files, a 'css' folder for cascading style sheets, a 'lib' folder for third party libraries such as bootstrap, and an 'img' folder for images.
Put the folder structure together as shown - we'll use the same layout in the future.
Step 2 - The HTML
Just like in the previous tutorial, we'll have a very simple webpage for our game - mostly what it will do is include some JavaScript and start the game running.
Here's how we'll start.
<!DOCTYPE html>
<html>
<head>
<title>Space Invaders</title>
<link rel="stylesheet" type="text/css" href="css/core.css">
<link rel="stylesheet" type="text/css" href="css/typeography.css">
<style>
body, html {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<!--
<script src="js/starfield.js"></script>
<script src="js/spaceinvaders.js"></script>
<script>
</script>
</body>
</html>
We start with the html doctype - this is an HTML5 page. Then we set the title and include two stylesheets. These stylesheets I use a lot for simple tutorials, they just clean up some of the default browser styles, they've very simple, you can get them at github.com/dwmkerr/html5base, or you can get them from the download.
Next we have a little css for the body and html elements - what we're doing here is making sure we're not going to have any scrollbars, as this game will just fill the window. We've also got a placeholder for styles we'll add later.
After this there's an HTML comment showing where we'll put the game elements.
To finish up, we've included the starfield script from last time (this will be the background of the game) and a 'spaceinvaders.js' file, which we'll create shortly. Then there's a script block for anything else we need. Now we're good to go!
Step 3 - A Starfield Background
See how wildly useful these tutorials are? We've already found a use for the first one. We'll start by adding a starfield, the same one we made in the last tutorial. (If you didn't follow that tutorial, you can get the starfield here github.com/dwmkerr/starfield).
We'll add a div that'll hold the starfield. Add the following HTML to the page:
<!--
<!--
<div id="starfield"></div>
This div will contain the starfield. Now we can style it in the style element of the header.
#starfield {
width:100%;
height:100%;
z-index: -1;
position: absolute;
left: 0px;
top: 0px;
}
Finally, in the script block we can actually create the starfield.
var container = document.getElementById('starfield');
var starfield = new Starfield();
starfield.initialise(container);
starfield.start();
As we went through the starfield in detail in the last tutorial, we don't need to see it again here.
At this stage we have a simple animated starfield. Now to get to work on the game.
Step 4 - A Game Engine
Now we're coming to the fun stuff. We are going to need to do a few different things in our game - show a welcome screen, show a screen for when the game is over, run the game, handle input and so on.
When we're writing the code, the first thing we should try and do is make sure that we can separate the game into different 'states', and have a simple game engine that can transition from one state to another, delegate user input to a state and run the drawing and update loop. That's exactly what we're going to do.
To keep things simple, I'm making this a Space Invaders Game Engine, not a general, re-usable game engine, that's fine as we're learning, rewriting code is good and trying things in different ways is good.
So let's have a think about what a game engine needs:
- We should be able to have multiple states.
- We should be able to move from one state to another (e.g. the 'welcome' screen to the 'play' screen).
- A state should be able to draw itself.
- A state should be able to update itself (e.g. on some arbitrary tick we advance the invaders etc).
- A state should be able to be 'pushed' - for example a paused screen is a state on top of whatever state is below it. Unpausing simply pops the state.
With these as some initial requirements we can get to work. Create a 'spaceinvaders.js' file in the 'js' folder. Now create a 'game' class.
function Game() {
this.config = {
gameWidth: 400,
gameHeight: 300,
fps: 50
};
this.lives = 3;
this.width = 0;
this.height = 0;
this.gameBound = {left: 0, top: 0, right: 0, bottom: 0};
this.stateStack = [];
this.pressedKeys = {};
this.gameCanvas = null;
}
This class is so far just data. We have some config (which we'll add to). The config is the game settings - how fast the invaders will move and so on. The actual state of the whole game then follows (the size of the viewport etc).
Last but not least, we have an array that we'll use as a stack for the game states, as well as an object to hold the keys that are currently pressed, and the canvas to render the game.
We can now make a function that initialises the game. All we need as input is a canvas to render to.
Game.prototype.initialise = function(gameCanvas) {
this.gameCanvas = gameCanvas;
this.width = gameCanvas.width;
this.height = gameCanvas.height;
this.gameBounds = {
left: gameCanvas.width / 2 - this.config.gameWidth / 2,
right: gameCanvas.width / 2 + this.config.gameWidth / 2,
top: gameCanvas.height / 2 - this.config.gameHeight / 2,
bottom: gameCanvas.height / 2 + this.config.gameHeight / 2,
};
};
In the initialise function, we store the game canvas (as we're going to want to use it later on) and set the width and height of the game. We also create the 'bounds' of the game - think of these as a rectangle that the game is played in. We set the dimensions of the game in the config, and then we plot things relative to the game bounds.
Right - our game class needs to be able to return it's state. Let's create a currentState function:
Game.prototype.currentState = function() {
return this.stateStack.length > 0 ? this.stateStack[this.stateStack.length - 1] : null;
If we have anything in the stack (which is actually an array, but arrays are flexible enough in JavaScript to use as stacks at a pinch), we return the top item (i.e. the last item in the array). Otherwise we return null.
We can get a state object, now we want to be able to move to a state.
Game.prototype.moveToState = function(state) {
if(this.currentState()) {
if(this.currentState().leave) {
this.currentState().leave(game);
}
this.stateStack.pop();
}
if(state.enter) {
state.enter(game);
}
this.stateStack.push(state);
};
This is where things are getting smarter. This is what moving to a state does:
- If we're already in a state, we check if the state object has a function called 'leave'. If it does, we call it. This means our state objects can choose to be notified if they're about to exit.
- If we're already in a state, pop it from the state stack.
- If there's a function named 'enter' for the new state, call it. This means states can choose to be notified if they're about to be entered.
- Now we push our new state onto the stack.
So the take-away here is this - moveToState
replaces the top of the state stack with a new state - and states can know when they're entering or leaving.
We can use exactly the same principals to quickly wire up pushState and popState functions:
Game.prototype.pushState = function(state) {
if(state.enter) {
state.enter(game);
}
this.stateStack.push(state);
};
Game.prototype.popState = function() {
if(this.currentState()) {
if(this.currentState().leave) {
this.currentState().leave(game);
}
this.stateStack.pop();
}
};
These functions work with the same principals as the moveToState
function.
For our game to do anything, we'll need some kind of loop that's running, telling the active state that it needs to draw and so on. So let's put together a global gameLoop
function that does just this:
function gameLoop(game) {
var currentState = game.currentState();
if(currentState) {
var dt = 1 / game.config.fps;
var ctx = game.gameCanvas.getContext("2d");
if(currentState.update) {
currentState.update(game, dt);
}
if(currentState.draw) {
currentState.draw(game, dt, ctx);
}
}
}
This function is key.
- First, get the current game state.
- Now work out how much time is in one 'tick' of the loop. This is one over the FPS - if we loop ten times per second, each tick is 100 milliseconds.
- Get a drawing context from the canvas (this is explained in Part 1).
- If there is a function called 'update' in the state, call it, passing the game object and the amount of time that's passed.
- If there's a function called 'draw' in the state, call it, passing the game object, the amount of time that's passed and the drawing context.
Now we just need to call this function on a timer. We can create a 'start' method in the Game for that:
Game.prototype.start = function() {
this.moveToState(new WelcomeState());
this.lives = 3;
this.config.debugMode = /debug=true/.test(window.location.href);
var game = this;
this.intervalId = setInterval(function () { gameLoop(game);}, 1000 / this.config.fps);
};
By now with what we know about states, we can see how this goes. We start the game by moving into a new instance of the 'WelcomeState
' class (which we'll create next!), we set the number of lives to three, then set a timer to call the gameLoop
based on the FPS config setting. Let's see how the Welcome State looks.
Step 5 - The Welcome State
The first state is one of the easiest, because all it will do is show the title of the game. We start by creating a class for the state:
function WelcomeState() {
}
The Welcome State is so simple it doesn't even have any data members. Now we can create a draw function:
WelcomeState.prototype.draw = function(game, dt, ctx) {
ctx.clearRect(0, 0, game.width, game.height);
ctx.font="30px Arial";
ctx.fillStyle = '#ffffff';
ctx.textBaseline="center";
ctx.textAlign="center";
ctx.fillText("Space Invaders", game.width / 2, game.height/2 - 40);
ctx.font="16px Arial";
ctx.fillText("Press 'Space' to start.", game.width / 2, game.height/2);
};
Again, we can look back to Part 1 for more detail on the canvas context, but there's nothing complicated here - we clear the drawing surface, write out "Space Invaders" and ask the user to press the spacebar.
WelcomeState.prototype.keyDown = function(game, keyCode) {
if(keyCode == 32) {
game.moveToState(new LevelIntroState(game.level));
}
};
Now we can create a keyDown function for the state - if the keycode is space, we move to the LevelIntroState.
The only problem here is that keyDown is never called, because it's not in the game engine. That's something we can add now:
Game.prototype.keyDown = function(keyCode) {
this.pressedKeys[keyCode] = true;
if(this.currentState() && this.currentState().keyDown) {
this.currentState().keyDown(this, keyCode);
}
};
Game.prototype.keyUp = function(keyCode) {
delete this.pressedKeys[keyCode];
if(this.currentState() && this.currentState().keyUp) {
this.currentState().keyUp(this, keyCode);
}
};
The GameEngine can be notified that a key has been pressed or released. Once that happens, we see if the current state has a keyDown or keyUp function - if so we call it. We also keep track of each key that is pressed in an object, so that if the user pressed multiple keys, states can look at the game.pressedKeys object and see what is pressed.
JavaScript Tip: The 'delete' keyword can be used to remove a property from an object.
We've created the welcome state and we've got a game start function, so let's go back to the index and actually add the game.
Here's the HTML (what's new is in bold):
<div id="starfield"></div>
<div id="gamecontainer">
<canvas id="gameCanvas"></canvas>
</div>
Here's the CSS:
#gamecontainer {
width: 800px;
margin-left: auto;
margin-right: auto;
}
Lastly, here's the JavaScript:
var container = document.getElementById('starfield');
var starfield = new Starfield();
starfield.initialise(container);
starfield.start();
var canvas = document.getElementById("gameCanvas");
canvas.width = 800;
canvas.height = 600;
var game = new Game();
game.initialise(canvas);
game.start();
window.addEventListener("keydown", function keydown(e) {
var keycode = e.which || window.event.keycode;
if(keycode == 37 || keycode == 39 || keycode == 32) {
e.preventDefault();
}
game.keyDown(keycode);
});
window.addEventListener("keyup", function keydown(e) {
var keycode = e.which || window.event.keycode;
game.keyUp(keycode);
});
This is not as complex as it might seem. We create the Game object, initialise it with a canvas, start the game and tell it when keys are pressed. We don't let the window process space, left or right otherwise it tries to move the viewport around, and we don't want that.
Our game now has a welcome screen - we're getting there!
Step 6 - The Level Intro
The Welcome State was trivial - it didn't need the update function or enter or leave. Now we'll create a state that we use to show a three second countdown before the level starts. Again, we can create a class for the state:
function LevelIntroState(level) {
this.level = level;
this.countdownMessage = "3";
}
This state actually has state of its own, it knows what level it's counting down for and the message it's showing. We can create a draw function next:
LevelIntroState.prototype.draw = function(game, dt, ctx) {
ctx.clearRect(0, 0, game.width, game.height);
ctx.font="36px Arial";
ctx.fillStyle = '#ffffff';
ctx.textBaseline="middle";
ctx.textAlign="center";
ctx.fillText("Level " + this.level, game.width / 2, game.height/2);
ctx.font="24px Arial";
ctx.fillText("Ready in " + this.countdownMessage, game.width / 2, game.height/2 + 36);
};
This is trivial - we just show a message saying 'Ready in X' with X being the countdown message.
Now we can create an update function.
Game Tip: What's the update function? Update is called in the loop like Draw, but it doesn't draw anything - it updates the state of the game. We could do this in draw, but draw should just faithfully render the current state untouched. Why is this? Well we could actually call draw and update at different frequencies - for example if drawing is expensive we can call it ten times less often than update - but still have the state of the system be updated at more regular intervals.
LevelIntroState.prototype.update = function(game, dt) {
if(this.countdown === undefined) {
this.countdown = 3;
}
this.countdown -= dt;
if(this.countdown < 2) {
this.countdownMessage = "2";
}
if(this.countdown < 1) {
this.countdownMessage = "1";
}
if(this.countdown <= 0) {
game.moveToState(new PlayState(game.config, this.level));
}
};
If we don't have a countdown number, set it to three (seconds). Now remove the time that's passed (dt is in seconds). Every time the countdown number gets below 2 or 1, we can update the countdown message. When it gets to zero, we can move into the Play State - the actual game, passing the level we've been told we're counting down for.
That's it - the level intro state is done. As we already transition to it when space is pressed on the welcome state, we can run up the page, press space and see it working:
Step 7 - The Play State
This is the big one. The play state knows what level it is when it is created, and that's about it. We've got to make sure that we create the invaders, position them, create the ship, position it, respond to mouse movement, respond to time passing, handle bombs from the invaders, rockets from the ship and the score. But hey - this is JavaScript and knocking stuff together quickly is one of the things this language is good at.
First - the lie. When we made the game state I didn't show a bunch of the properties in the Game config. These are used to adjust how fast things move, or accelerate, or change and so on. It wasn't needed at the time but now we'd better take a look, because from the Play State constructor we can see we take a copy of the Game config cause we use so much of it:
function PlayState(config, level) {
this.config = config;
this.level = level;
this.invaderCurrentVelocity = 10;
this.invaderCurrentDropDistance = 0;
this.invadersAreDropping = false;
this.lastRocketTime = null;
this.ship = null;
this.invaders = [];
this.rockets = [];
this.bombs = [];
}
The actual constructor isn't too bad - we take a copy of the game config reference, set the current velocity of the invaders, the current drop distance (that's how far they've moved downwards when they hit the edge of the screen), a flag for whether they're dropping, a time for when the last rocket was fired (so we can limit the number of rockets per second) and then create a ship, set of invaders, set of rockets and set of bombs.
Just as as reference, here's the actual game config - we'll see what most of it is for as we go through, but essentially we can adjust aspects of how the game runs with them:
function Game() {
this.config = {
bombRate: 0.05,
bombMinVelocity: 50,
bombMaxVelocity: 50,
invaderInitialVelocity: 25,
invaderAcceleration: 0,
invaderDropDistance: 20,
rocketVelocity: 120,
rocketMaxFireRate: 2,
gameWidth: 400,
gameHeight: 300,
fps: 50,
debugMode: false,
invaderRanks: 5,
invaderFiles: 10,
shipSpeed: 120,
levelDifficultyMultiplier: 0.2,
pointsPerInvader: 5
};
}
We're going to be dealing with a ship, rockets, bombs and invaders. Coming from a static background this makes me want types for them, so that's what I'll make:
function Ship(x, y) {
this.x = x;
this.y = y;
this.width = 20;
this.height = 16;
}
function Rocket(x, y, velocity) {
this.x = x;
this.y = y;
this.velocity = velocity;
}
function Bomb(x, y, velocity) {
this.x = x;
this.y = y;
this.velocity = velocity;
}
function Invader(x, y, rank, file, type) {
this.x = x;
this.y = y;
this.rank = rank;
this.file = file;
this.type = type;
this.width = 18;
this.height = 14;
}
So each entity has a position, and the larger ones have a size. The invader also knows its rank and file (where it is in the grid).
Now we'll go into the first big function of the state, enter. This is called when we start each level:
PlayState.prototype.enter = function(game) {
this.ship = new Ship(game.width / 2, game.gameBounds.bottom);
We create the ship at the bottom middle of the game bounds.
The code below looks complex but isn't too much - it makes sure things like the invader speed and bomb speed gets a bit faster each level, but that's it.
var levelMultiplier = this.level * this.config.levelDifficultyMultiplier;
this.shipSpeed = this.config.shipSpeed;
this.invaderInitialVelocity = this.config.invaderInitialVelocity + (levelMultiplier * this.config.invaderInitialVelocity);
this.bombRate = this.config.bombRate + (levelMultiplier * this.config.bombRate);
this.bombMinVelocity = this.config.bombMinVelocity + (levelMultiplier * this.config.bombMinVelocity);
this.bombMaxVelocity = this.config.bombMaxVelocity + (levelMultiplier * this.config.bombMaxVelocity);
The game is actually very highly configurable because of code like this - it had to be so I could find sensible values and multipliers to get a good feel and increase in difficulty as the levels progress.
var ranks = this.config.invaderRanks;
var files = this.config.invaderFiles;
var invaders = [];
for(var rank = 0; rank < ranks; rank++){
for(var file = 0; file < files; file++) {
invaders.push(new Invader(
(game.width / 2) + ((files/2 - file) * 200 / files),
(game.gameBounds.top + rank * 20),
rank, file, 'Invader'));
}
}
this.invaders = invaders;
this.invaderCurrentVelocity = this.invaderInitialVelocity;
this.invaderVelocity = {x: -this.invaderInitialVelocity, y:0};
this.invaderNextVelocity = null;
};
We finish the enter function by creating an invader at each rank and file. There's some arithmatic to position and space them nicely. We also store the current velocity of the invaders. There's a 'next velocity' too - we use that when we move them downwards and need to decide where to move them afterwards.
Now for update. This is where all of the state for the state is updated.
PlayState.prototype.update = function(game, dt) {
if(game.pressedKeys[37]) {
this.ship.x -= this.shipSpeed * dt;
}
if(game.pressedKeys[39]) {
this.ship.x += this.shipSpeed * dt;
}
if(game.pressedKeys[32]) {
this.fireRocket();
}
if(this.ship.x < game.gameBounds.left) {
this.ship.x = game.gameBounds.left;
}
if(this.ship.x > game.gameBounds.right) {
this.ship.x = game.gameBounds.right;
}
The first thing we do is see if the left or right keys are pressed. If so, we nudge the ship. If space is pressed, we call the fireRocket function, which we'll come to later. We also make sure we don't ever move the ship past the game bounds.
Now we can move each bomb downwards (unless it has gone out of bounds in which case we remove it). We can also move each rocket upwards (again, unless it's out of bounds, when we can remove it).
for(var i=0; i<this.bombs.length; i++) {
var bomb = this.bombs[i];
bomb.y += dt * bomb.velocity;
if(bomb.y > this.height) {
this.bombs.splice(i--, 1);
}
}
for(i=0; i<this.rockets.length; i++) {
var rocket = this.rockets[i];
rocket.y -= dt * rocket.velocity;
if(rocket.y < 0) {
this.rockets.splice(i--, 1);
}
}
So we've handled the ship movement and the bomb and rocket movement.
Now for the really ugly part - moving the invaders.
var hitLeft = false, hitRight = false, hitBottom = false;
for(i=0; i<this.invaders.length; i++) {
var invader = this.invaders[i];
var newx = invader.x + this.invaderVelocity.x * dt;
var newy = invader.y + this.invaderVelocity.y * dt;
if(hitLeft === false && newx < game.gameBounds.left) {
hitLeft = true;
}
else if(hitRight === false && newx > game.gameBounds.right) {
hitRight = true;
}
else if(hitBottom === false && newy > game.gameBounds.bottom) {
hitBottom = true;
}
if(!hitLeft && !hitRight && !hitBottom) {
invader.x = newx;
invader.y = newy;
}
}
if(this.invadersAreDropping) {
this.invaderCurrentDropDistance += this.invaderVelocity.y * dt;
if(this.invaderCurrentDropDistance >= this.config.invaderDropDistance) {
this.invadersAreDropping = false;
this.invaderVelocity = this.invaderNextVelocity;
this.invaderCurrentDropDistance = 0;
}
}
if(hitLeft) {
this.invaderCurrentVelocity += this.config.invaderAcceleration;
this.invaderVelocity = {x: 0, y:this.invaderCurrentVelocity };
this.invadersAreDropping = true;
this.invaderNextVelocity = {x: this.invaderCurrentVelocity , y:0};
}
if(hitRight) {
this.invaderCurrentVelocity += this.config.invaderAcceleration;
this.invaderVelocity = {x: 0, y:this.invaderCurrentVelocity };
this.invadersAreDropping = true;
this.invaderNextVelocity = {x: -this.invaderCurrentVelocity , y:0};
}
if(hitBottom) {
this.lives = 0;
}
I apologise! This is really rather ugly, and I'm sure it could be done better. We move each invader by the current velocity, checking to see if we've hit the left, right or bottom bounds. If it's left or right, we start moving the invaders down and tell them where to move after that. If they hit the bottom we're dead. This looks complex but isn't really, it's just fiddly. But I'm sure it could be done more cleanly.
Now we can do some good old fashioned collision detection:
for(i=0; i<this.invaders.length; i++) {
var invader = this.invaders[i];
var bang = false;
for(var j=0; j<this.rockets.length; j++){
var rocket = this.rockets[j];
if(rocket.x >= (invader.x - invader.width/2) && rocket.x <= (invader.x + invader.width/2) &&
rocket.y >= (invader.y - invader.height/2) && rocket.y <= (invader.y + invader.height/2)) {
this.rockets.splice(j--, 1);
bang = true;
game.score += this.config.pointsPerInvader;
break;
}
}
if(bang) {
this.invaders.splice(i--, 1);
}
}
We go through every invader and see if it's been hit by a rocket. If so, we remove the rocket and invader then update the score.
As we've been a bit cruel to the invaders there, we'll next find each front rank invader and give it a chance to drop a bomb:
var frontRankInvaders = {};
for(var i=0; i<this.invaders.length; i++) {
var invader = this.invaders[i];
if(!frontRankInvaders[invader.file] || frontRankInvaders[invader.file].rank < invader.rank) {
frontRankInvaders[invader.file] = invader;
}
}
for(var i=0; i<this.config.invaderFiles; i++) {
var invader = frontRankInvaders[i];
if(!invader) continue;
var chance = this.bombRate * dt;
if(chance > Math.random()) {
this.bombs.push(new Bomb(invader.x, invader.y + invader.height / 2,
this.bombMinVelocity + Math.random()*(this.bombMaxVelocity - this.bombMinVelocity)));
}
}
Again, the game settings come in here as the rate of bomb drops increases with each level.
We've checked for rockets and invaders, now we can check for bombs and the ship:
for(var i=0; i<this.bombs.length; i++) {
var bomb = this.bombs[i];
if(bomb.x >= (this.ship.x - this.ship.width/2) && bomb.x <= (this.ship.x + this.ship.width/2) &&
bomb.y >= (this.ship.y - this.ship.height/2) && bomb.y <= (this.ship.y + this.ship.height/2)) {
this.bombs.splice(i--, 1);
game.lives--;
}
}
And while we're at it - let's make sure that if an invader touches the ship it's game over too:
for(var i=0; i<this.invaders.length; i++) {
var invader = this.invaders[i];
if((invader.x + invader.width/2) > (this.ship.x - this.ship.width/2) &&
(invader.x - invader.width/2) < (this.ship.x + this.ship.width/2) &&
(invader.y + invader.height/2) > (this.ship.y - this.ship.height/2) &&
(invader.y - invader.height/2) < (this.ship.y + this.ship.height/2)) {
game.lives = 0;
game.sounds.playSound('explosion');
}
}
Programming Tip: In this code I'm repeatedly looping through sets. It could be improved greatly by jamming more logic into less loops (this is called loop jamming) but it would make the tutorial harder to read. But always be suspicious of code like the above - with large numbers of invaders or bombs we're looping too many times. Always keep an eye out for redundant looping.
We've just about finished, the last thing is to see if we've run out of lives and end the game if so, or see if we've run out of invaders and countdown the next level if so:
if(game.lives <= 0) {
game.moveToState(new GameOverState());
}
if(this.invaders.length === 0) {
game.score += this.level * 50;
game.level += 1;
game.moveToState(new LevelIntroState(game.level));
}
};
That's our update function. I promised I'd show the 'fireRocket' function too:
PlayState.prototype.fireRocket = function() {
if(this.lastRocketTime === null || ((new Date()).valueOf() - this.lastRocketTime) > (1000 / this.config.rocketMaxFireRate))
{
this.rockets.push(new Rocket(this.ship.x, this.ship.y - 12, this.config.rocketVelocity));
this.lastRocketTime = (new Date()).valueOf();
}
};
Surprisingly clunky - we need to make sure that the user can't just hold down the space key and fire as many rockets as they want, so we limit the fire rate in this function.
OK so we have Space Invaders, but only as a system in memory, now we need to render it. As we're just looping through the game entities and drawing primitives, I'm not going through this blow-by-blow:
PlayState.prototype.draw = function(game, dt, ctx) {
ctx.clearRect(0, 0, game.width, game.height);
ctx.fillStyle = '#999999';
ctx.fillRect(this.ship.x - (this.ship.width / 2), this.ship.y - (this.ship.height / 2), this.ship.width, this.ship.height);
ctx.fillStyle = '#006600';
for(var i=0; i<this.invaders.length; i++) {
var invader = this.invaders[i];
ctx.fillRect(invader.x - invader.width/2, invader.y - invader.height/2, invader.width, invader.height);
}
ctx.fillStyle = '#ff5555';
for(var i=0; i<this.bombs.length; i++) {
var bomb = this.bombs[i];
ctx.fillRect(bomb.x - 2, bomb.y - 2, 4, 4);
}
ctx.fillStyle = '#ff0000';
for(var i=0; i<this.rockets.length; i++) {
var rocket = this.rockets[i];
ctx.fillRect(rocket.x, rocket.y - 2, 1, 4);
}
};
We now have the core part of the game working;
Step 8 - The Other Stuff
There are a few bits and pieces that we don't need to go into. There's a game over state and a pause state, there's also some code to play sounds, but we've really seen the core of what's going on - any more would certainly be overkill.
The code is on GitHub - fork it, play with it and have fun. In the next tutorial we'll work with a canvas but also introduce some more HTML elements and use our first framework - AngularJS.
As always, questions and comments are most welcome!