How to Build Asteroids with the Impact HTML5 Game Engine
Rob Hawkes | April 6, 2011
Over the past couple years or so there has been a dramatic rise in the number of HTML5 games around on the Web, thanks in no small part to the HTML5 gaming engines that are making their development much easier.
In this tutorial, I'm going to show you how to build a simple space-based asteroids game using the Impact game engine, one of the more robust engines out at the moment.
Impact is a commercial engine, so you'll need to purchase a license before you can use it, but that doesn't stop you from at least following along with the tutorial to get a feel for how it works.
You'll also need these game assets within the tutorial, so I suggest that you download them now if you plan to follow along.
Sometimes spending money is a good thing
Let's get this out of the way; Impact isn't free. If you're anything like me, you're probably a little disappointed with that statement. After all, we're using open Web technologies here, so why charge for something that is built on top of free technology. Right? Well, to put it simply; the developer needs to make a living. On top of that, and probably most importantly; the Impact engine is an immensely solid and well documented engine. I'd even go as far as saying it's the best HTML5 gaming engine that I've had the chance to play with to date, albeit not 100% perfect (what is?).
I'm entirely certain that the price of the engine is a reflection of the time and effort that has gone into constructing and supporting it. In short, if you can't afford the $99 for a license, then I suggest you take a look at some of the free HTML5 gaming engines instead.
Overview of the Impact game engine
As I mentioned previously, Impact is a solid game engine that has obviously had a lot of time and effort put into its development. It runs on all the major browsers that support HTML5 canvas, as well as the iPhone, iPod Touch, and the iPad. In my eyes, what puts Impact head and shoulders above the competition is the thorough documentation, the tutorials, as well as the helpful support that can be accessed through the forums. Impact certainly has a feeling of overall quality about it that some of the other engines don't have.
Impact has a whole host of functionality built in, like entities (objects within the game), physics, animations, user input, collision detection, audio, and even a full blown level editor (more on that in a moment). This is by no means a definitive list of what the engine can do, but I hope it at least sheds some light on how much of the tedious work it can take off your hands. This is helped by the way the engine has been constructed, using an object oriented approach, which allows you to effectively plug and play with new features in your game by extending on what's already built in. It's quick & easy and just what you want as a busy game developer.
The Weltmeister Level Editor is one of the crowning jewels of Impact, and is perhaps one of the most visually impressive features of the engine. This level editor is effectively a GUI that allows you to construct your game world, from simply designing the levels, to placing objects and defining how they interact with each other. It's pretty impressive!
It's important to point out that you won't be using the Weltmeister Level Editor in this tutorial, as I want to teach you the raw code, but I'd recommend watching this video tutorial about the editor to get a feel for what it can do.
What you'll be making
In this tutorial you'll be making a simple space-based game in which you need to avoid the asteroids floating around. You'll be leaving out features like audio and shooting for now, as they will just over-complicate matters for now, but rest assured that they are features that you can definitely add later in your own time without much hassle.
Why don't you check out this video of the game in action?
Setting up Impact
Before you can get your hands dirty with the game you need to set up the Impact engine. It's relatively painless, but I'm going to cover it in detail because it's important to know what's going on. I'd definitely check out the documentation page about setting up Impact for a much more in-depth walkthrough.
Using a domain for development
One of the major things to bear in mind when developing with Impact is that you need to be working on a Web server, and you need to access the game with a domain name. You can either use an online server, or a local development server through something like MAMP (Mac), WAMP (Windows), or XAMPP (cross-platform). This requirement is due to security issues in JavaScript and it's nothing to do with the Impact engine.
Downloading and unpacking
After you purchase Impact you'll be sent an email with your personal download link and license key. I hope you keep it safe! This download link is tied to your account, so if you lose it you'll have to get in touch with the developer to get it resent. The first steps are to download your personal copy of the engine, unpack the zip file, open up the impact folder, and move the files and folders inside to your Web server. You'll know if all went well because you'll see a message on the screen when you load the index.html file in your browser.
The files that you just copied are the core engine files (the lib/impact folder), as well as some example files that you'll be using to create your game (the lib/game folder). The other files that you see are related to the Weltmeister Level Editor (the lib/weltmeister folder), as well as some tools to help when packing up the game for public release (the tools folder).
For the duration of this tutorial you need only worry about the lib/game folder, as well as the index.html file.
Setting up the HTML and CSS
The default HTML file uses inline CSS, so let's perform some tweaks and move the CSS out into a separate file.
Open up index.html, remove everything, and add the following code:
<!DOCTYPE html>
<html>
<head>
<title>Impact Asteroids Game</title>
<meta charset="utf-8">
<link href="game.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="lib/impact/impact.js"></script>
<script type="text/javascript" src="lib/game/main.js"></script>
</head>
<body>
<canvas id="gameCanvas"></canvas>
</body>
</html>
Next, create a new file called game.css and put it in the same direction as index.html. Add the following code within the CSS file:
* { margin: 0; padding: 0; }
html, body { height: 100%; width: 100%; }
canvas { display: block; }
body {
background: #000;
}
#gameCanvas {
height: 512px;
left: 50%;
margin: -256px 0 0 -384px;
position: relative;
top: 50%;
width: 768px;
}
All this does is provide a really basic CSS reset, as well as positioning the game centrally in the browser. Notice how you've set the width and height of the game within the CSS to a pretty specific size. I'll explain why you chose those dimensions in a moment.
Setting up the main game file
If you refresh the browser you'll notice all you see is a black background. This is because you just broke the game by fiddling with the HTML and changing the id of the canvas element. Whoops! Let's sort that out.
First, open up the lib/game/main.js file, which is the main file for your game. I'll explain what the various sections of this file do in a moment, but for now we just want to make a couple of changes to get that message back on your screen.
Find the call to ig.main
at the end of the file and replace it with the following:
ig.main( "#gameCanvas", MyGame, 60, 768, 512, 1 );
All that you've done here is change the first argument to point to the new id for the canvas element, the one that you changed before. Other changes have been made to the fourth and fifth arguments to reflect the new dimensions of the game, and also to the last argument to stop the game from being scaled to twice the size. You're using specific dimensions because the engine is tile-based, and the tiles that you're going to be using are mainly 64x64 pixels - 768 and 512 are just nicely divisible by 64, that's all.
Check out the documentation for the ig.main
function if you want to learn more about how it works.
If you refresh the browser window you should now get the previous message showing up again, although this time it should look a little smaller because you stopped the game from being scaled up.
Modules
It's worth spending a few moments to explain how modules work, seeing as they will be what you use to create your game. Here is the module code from the main.js file that you just looked at, with some parts removed that aren't important to cover yet:
ig.module(
"game.main"
)
.requires(
"impact.game",
"impact.font"
)
.defines(function(){
// Main module code
});
The first section, ig.module
, defines a new module, the name of which is declared on the second line - in this case, "game.main". The name is representative of the folder structure, so "game.main" refers to a file called main.js that exists in the lib/game folder. It's pretty straight forward. The second section, requires, lists modules that are required to be loaded before this module is run. Each module follows the same naming guideline as before, so "impact.game" refers to the game.js file in the lib/impact folder. Finally, the defines
section is where the code for the module is placed. This section won't run until all the required modules have loaded.
Creating the game entities
Now that the main file is ready, it's time to create the entities for your game. Entities are anything in the game that are interactive, like the player, or enemies.
Asteroid entity
You're going to set up your entities as modules, so the first thing to do is create a file called asteroid.js within the lib/game/entities folder. Open up the file and add the following code to set up the entity module:
ig.module(
"game.entities.asteroid"
).requires(
"impact.entity"
).defines(function() {
// Subclassed from ig.Enitity
EntityAsteroid = ig.Entity.extend({
// Set some of the properties
size: {x: 64, y: 64},
// Entity type
type: ig.Entity.TYPE.B,
init: function(x, y, settings) {
// Call the parent constructor
this.parent(x, y, settings);
},
// This method is called for every frame on each entity.
update: function() {
// Call the parent update() method to move the entity according to its physics
this.parent();
}
});
});
This is the core code behind the majority of entities, which you should hopefully recognize most of this from the main.js file you just looked at - it's a module.
You name the entity module at the top in ig.module
, calling it "game.entities.asteroid", and then declare in requires
that the "impact.entity" module needs to be loaded. This module provides all the core functionality for creating entities.
Within the defines
section you set up your entity by ending the entity module, like so:
EntityAsteroid = ig.Entity.extend({
The first part is the name of the entity, and the second is just saying that you want to extend the base entity class from Impact.
Within the call to ig.Entity.extend
you place all the code to define the asteroid entity, starting with the sizeand type properties. The size property refers to the dimensions of the entity, and doesn't have to match up with the size of the sprite image that is used to display it (you'll notice this when you create the player entity). Setting type allows you to group entities together into either an A group, or B group. For example, this could be used to separate dangerous entities (B) from friendly entities (A).
The init
function is called when the entity is created, and update is called on every loop of the game, before anything is drawn.
Right now, the asteroid entity doesn't do anything, so let's get it set up and visible within the game.
Add the following code underneath where you set the type
property:
// Load an animation sheet
animSheet: new ig.AnimationSheet("media/asteroid.png", 64, 64),
This sets up the sprite for the asteroid, and allows you to animate it if you wish. In your case you’re using the asteroid sprite from the game assets provided with this tutorial, but feel free to create your own if you wish.
Next up is to define an animation for the sprite, which is a simple in this case because you don't want the sprite to animate at all. Add the following code underneath this.parent
in the init
function:
// Add animations for the animation sheet
this.addAnim("idle", 1, [0]);
The first argument names the animation and could be whatever you want, the second is the time to keep each frame of the animation visible in seconds, and the third is an array of frame numbers for the animation. In your case there is only one frame, which is 0 (the first element of an array is always 0).
You won't be able to see anything yet, so jump into the main.js file and add the following code within the requires
section at the top (remember to put a comma after the previous file):
"game.entities.asteroid"
And add the following within the init
function:
var asteroidSettings;
for (var i = 0; i < 8; i++) {
asteroidSettings = {vel: {x: 100-Math.random()*200, y: 100-Math.random()*200}};
this.spawnEntity(EntityAsteroid, ig.system.width/2, ig.system.height/2, asteroidSettings);
};
This will set up eight asteroids within the game at the middle of the screen, each with a random velocity. At least, it would if you added the asteroid sprite image to the game. To do that, move asteroid.png from the game assets provided and place it within the media folder.
If all went well, you should see a collection of asteroids dispersing from the middle of the screen. Result!
While you're in main.js, remove all the code within the draw function apart from this.parent
. This will remove the message from the screen, which you no longer require.
Boundary checks
The problem at the moment is that the asteroids disappear off the edge of the screen and never come back again. This is a bad thing. Jump back into the asteroids.js entity file and adding the following code underneath this.parent
in the update function:
// Boundary checks
if (this.pos.x > ig.system.width) {
this.pos.x = -64;
} else if(this.pos.x < -64) {
this.pos.x = ig.system.width;
};
if (this.pos.y > ig.system.height) {
this.pos.y = -64;
} else if (this.pos.y < -64) {
this.pos.y = ig.system.height;
};
What this does is check the position of the asteroid at the end of each update and, if it's outside of the game area, moves the asteroid to the other side of the game. This gives the effect of an infinite world where entities cannot be lost off the edge.
Player entity
Now that the basic asteroid entity is created, it's time to set up the player entity. Fortunately, most of the core logic is the same.
Create a new file called player.js and place it within the lib/game/entities folder. Add the follow code:
ig.module(
"game.entities.player"
).requires(
"impact.entity"
).defines(function() {
// Subclassed from ig.Enitity
EntityPlayer = ig.Entity.extend({
// Set the dimensions and offset for collision
size: {x: 28, y: 50},
offset: {x: 18, y: 7},
// Entity type
type: ig.Entity.TYPE.A,
// Load an animation sheet
animSheet: new ig.AnimationSheet("media/player.png", 64, 64),
init: function(x, y, settings) {
// Call the parent constructor
this.parent(x, y, settings);
// Add animations for the animation sheet
this.addAnim("idle", 0.05, [0]);
this.addAnim("thrust", 0.05, [1,2]);
},
// This method is called for every frame on each entity.
update: function() {
// Call the parent update() method to move the entity according to its physics
this.parent();
// Boundary checks
if (this.pos.x > ig.system.width) {
this.pos.x = -64;
} else if(this.pos.x < -64) {
this.pos.x = ig.system.width;
};
if (this.pos.y > ig.system.height) {
this.pos.y = -64;
} else if (this.pos.y < -64) {
this.pos.y = ig.system.height;
};
}
});
});
There's nothing crazy new here. One difference is that the entity has a different name in the ig.module
section, and also in the defines
section. Another is that the size
property isn't 64x64 pixels anymore; it's now 28x50 pixels, and the animation sheet is looking for the player sprite (which you can find in the game assets with this tutorial. The smaller size
property represents the size of the rocket within the player sprite image, which you can see in this image:
The blue area is the area defined by the size
property, and it's position is defined by the offset
property. This is important for making sure the right area of the sprite is used when calculating collisions.
Finally, there are two animations being set up this time; one for when the player is standing still (idle), and another for when the player is moving (thrust):
this.addAnim("idle", 1, [0]);
this.addAnim("thrust", 0.05, [1,2]);
There are two frames in the thrust animation, meaning that the sprite will cycle from frame 1, to 2, and then back to 1 again. Each frame will be displayed for 0.05 seconds. You'll see this in a moment, but the effect will basically be a flickering flame.
The last step is to add the player entity into the main game file, just like you did with the asteroids earlier. Open up main.js and add the following line to the requires
section at the top (remember to add a comma after the previous file):
"game.entities.player"
Then add the following code above the init
function:
player: null,
And add the following underneath where you add the asteroids in the init
function:
// Load player entitiy
var playerSettings = {};
this.player = this.spawnEntity(EntityPlayer, 150, 150, playerSettings);
Nothing will show up yet so, just like with the asteroids, you'll need to move the player.png file from the game assets provided with this tutorial into the media folder.
Refresh the browser and you should see a little rocket in the top left hand corner. Cool!
Game background
Although this isn't a true entity, it's something we still need to set up. While you're still in main.js, add the following above the init
function:
background: new ig.Image("media/background.png"),
This sets up the background image, but it won't be drawn yet. To draw it you'll want to replace the this.parent
line in the draw
function with the following:
// Draw the background
this.background.draw(0, 0);
// Draw all entities
for(var i = 0; i < this.entities.length; i++) {
this.entities[i].draw();
};
This replaces the built in method for drawing game entities with one that you have a little more control over.
Still, before you can see it, you'll need to move the background.png file from the game assets into the media folder. The result should be a nice starry background to your game.
Adding keyboard controls
Even though you've added the player entity, you can't actually move it. That's not good, so let's work out how to add keyboard controls to the game.
Event listeners
The first step is to add the event listeners that will detect when a key is being pressed. Add the following code within main.js and place it at the top of the init
function:
ig.input.bind(ig.KEY.LEFT_ARROW, "left");
ig.input.bind(ig.KEY.RIGHT_ARROW, "right");
ig.input.bind(ig.KEY.UP_ARROW, "up");
ig.input.bind(ig.KEY.ENTER, "play");
It should be fairly obvious as to which keys you're listening for here; left, right, up for moving the player, and enter for resetting the game when it's over. The names given to each event listener (left, right, up, and play) can actually be changed to any string that you want. However, I suggest that you leave them be, as you'll be using them again in a moment.
Moving the player
The whole point of adding the keyboard controls is to move the player around the game. Doing that is surprisingly straight forward, and shows the ease at which Impact can handle these things.
Jump into the player.js entity file and add the following code underneath the offset
property at the top:
// Angle, in degrees for more rotation granularity
angle: 0,
// Thrust, dictating how much to accelerate
thrust: 0,
And add the following code above this.parent
into the update
function:
// "input.pressed" is called once for every key press
// "input.state" is called on every frame that the key is held down for
if (ig.input.state("left")) {
this.angle -= 3;
};
if (ig.input.state("right")) {
this.angle += 3;
};
if (ig.input.state("up")) {
// Accelerate the player in the right direction
this.accel.x = Math.sin(this.angle*Math.PI/180)*this.thrust;
this.accel.y = -(Math.cos(this.angle*Math.PI/180)*this.thrust);
this.currentAnim = this.anims.thrust;
} else {
this.accel.x = 0;
this.accel.y = 0;
this.currentAnim = this.anims.idle;
};
// Set the angle for the current animation
this.currentAnim.angle = this.angle*(Math.PI/180);
This may look like a lot, but it's surprisingly little for adding full control over the player movement. Let me explain what's going on.
The first conditional statement is looking at ig.input.state
("left"), which means that it is requesting the current state of the keyboard event that you called "left" in the previous section (this is why it was important not to change those names). If that key is currently being pressed down, then the ig.input.state
method will return true, else false.
In the case of the left and right keys, you're increasing or decreasing the angle property of the player depending on which key is being pressed. Then, in the case of the up key, you're using that angle, along with the thrust property, to calculate the acceleration of the player in the angle that they're headed. This is based on fairly standard trigonometry, which I suggest you learn about to fully understand.
Also, with the up key you're switching the animation of the player to the "thrust" animation if the key is being pressed (the player is moving), and then back to the "idle" animation if the key isn't being pressed (the player is still).
Finally, you use the angle
property of the player to set the angle of the sprite image. This line is important, as the image won't change angle otherwise. The crazy formula at the end is just converting the angle from degrees to radians, as that is what JavaScript works in.
If all went well, you should now be able to rotate the player with the left and right keys, and trigger the flame animation with the up key.
But why isn't the player moving forward? This is because you haven't set the thrust
property to something other than zero. Head back into the main.js file and change the playerSettings
line to the following:
var playerSettings = {thrust: 250, maxVel: {x: 300, y: 300}};
The maxVel
property is used to limit how fast the entity can travel, which is very useful for stopping the player from accelerating to near the speed of light.
Give this a go in the browser; you should now be able to fly the rocket around the screen with some pretty realistic feeling physics. Nice one!
Ending the game
So you now have a working game, but you can't die yet, which makes it a little pointless. The good news is that adding game over logic is super simple with Impact.
Open up the player.js entity file (for the last time, I promise), and the following code beneath the type
property at the top:
checkAgainst: ig.Entity.TYPE.B,
This should make sense; it's setting the player entity (which is type A) to check against objects that are type B (the asteroids). To make use of this you need to set up one more function, so add the following code above the update
function:
check: function(other) {
// Game over
this.health = 0;
},
The check
function is called whenever the current entity (the player) is overlapping another entity of the same type that is declared in the checkAgainst
property. If the two entities are overlapping, then it's time to kill the player, so you set the health
property to zero. This won't actually finish the game, but you'll add that logic next.
Open up the main.js file (for the last time) and add the following code below the background
property at the top:
gameOver: false,
You'll be using this property to let the game know when to be over. Add the following code above this.parent
in the update function:
// Run if the game is over
if (this.gameOver) {
if(ig.input.pressed("play") ) {
ig.system.setGame(MyGame);
};
// Return to stop anything else updating
return;
};
This will stop the game from updating if the game is over, and will reset the game using the ig.system.setGame
method when the enter key is pressed. Just a few lines of code can do so much!
To actually set the gameOver
property you'll want to add the following code beneath this.parent
in the update
function:
// Check for game over condition
if (this.player.health == 0) {
this.gameOver = true;
};
If the player is dead, the game will be set to end on the next loop. Simple.
The final step in the entire game (nearly done) is to display a message when the game is over. Add the following code beneath this.background.draw
in the draw
function:
// Game over screen
if (this.gameOver) {
this.font.draw("Game Over!", ig.system.width/2, 132, ig.Font.ALIGN.CENTER);
this.font.draw("Press Enter", ig.system.width/2, 232, ig.Font.ALIGN.CENTER);
this.font.draw("to Restart", ig.system.width/2, 272, ig.Font.ALIGN.CENTER);
// Return to stop anything else drawing
return;
};
This uses the same code that was included with the Impact code at the beginning to display that little demo message, except this time you're using it to display a message over three separate lines. However, if you run this in your browser you'll notice that the font is absolutely tiny!
To fix you need to replace the 04b03.font.png in the media folder with the one from the game assets that came with this tutorial, or create your own font image using the Impact Font Tool. The result should be a much larger, more readable font.
Summary
The game that you've created is simple, but it shows you most of the core functionality that the Impact engine provides out of the box. I'd definitely suggest taking the game further by adding audio, bullets to destroy the asteroids, better graphics for the asteroids, and perhaps even touch-based controls for mobile devices.
So, you've now reached the end of this journey with the Impact game engine. I hope you found it as interesting and exciting as I did the first time I used it. In my opinion, Impact is a very powerful engine, and one which you should definitely consider when developing HTML5 games in the future.
If you’d like to see the game in action, download the source code and run it yourself. Note that to run the demo, you’ll need to get a licensed copy of the Impact HTML5 game engine.
Good luck!
About the Author
Rob thrives on solving problems through code. He's addicted to visual programming, and can't get enough of HTML5 canvas. Most of his waking life is spent working on crazy projects involving all sorts of new and exciting technologies, both on-line and off. Aside from his practical work, Rob has written a book called "Foundation HTML5 Canvas", which is all about making games with the new Web technology. You should definitely pre-order it on Amazon! From June, Rob will be a Technical Evangelist for Mozilla.
Find Rob on:
- Twitter - @robhawkes
- Rob's Blog