HTML5 Game Development – Lesson 10
HTML5 Game Development – Lesson 10
Finally we can continue the series of articles on game development
in HTML5 using Canvas. Today I have prepared a new game – SkyWalker.
Basically – this is a flight simulator with a plane and multiple
enemies. Our goal – to get to the finish line. These are the key
features: using sprites for the plane and explosions, possibility to
press multiple keys (for example – you can move and attack at the same
time), a certain level length, enhanced collision detections (now
enemies can damage our plane), life and scores parameters.
Our previous article you can read here: Developing Your First HTML5 Game – Lesson 9.
Now you can check our demo and download the sources:
If you are ready – let’s start coding !
Step 1. HTML
As usual (for all canvas-based demos) we have a basic html markup:
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="utf-8" />
<meta name="author" content="Script Tutorials" />
<title>HTML5 Game Development - Lesson 10 (SkyWalker) | Script Tutorials</title>
<!-- add styles -->
<link href="css/main.css" rel="stylesheet" type="text/css" />
<!-- add scripts -->
<script src="js/jquery.js"></script>
<script src="js/script.js"></script>
</head>
<body>
<div class="container">
<canvas id="scene" width="700" height="700" tabindex="1"></canvas>
</div>
</body>
</html>Step 2. JS
Now, please create an empty file ‘script.js’ (in ‘js’ folder) and put this code inside (this is full JS code of our SkyWalker game). I will explain the main functionality below this code.
js/script.js
// inner variables
var canvas, ctx;
// images
var backgroundImage;
var oRocketImage;
var oExplosionImage;
var introImage;
var oEnemyImage;
var iBgShiftY = 9300; //10000 (level length) - 700 (canvas height)
var bPause = true; // game pause
var plane = null; // plane object
var rockets = []; // array of rockets
var enemies = []; // array of enemies
var explosions = []; // array of explosions
var planeW = 200; // plane width
var planeH = 110; // plane height
var iSprPos = 2; // initial sprite frame for plane
var iMoveDir = 0; // move direction
var iEnemyW = 128; // enemy width
var iEnemyH = 128; // enemy height
var iRocketSpeed = 10; // initial rocket speed
var iEnemySpeed = 5; // initial enemy speed
var pressedKeys = []; // array of pressed keys
var iScore = 0; // total score
var iLife = 100; // total life of plane
var iDamage = 10; // damage per enemy plane
var enTimer = null; // random timer for a new enemy
// -------------------------------------------------------------
// objects :
function Plane(x, y, w, h, image) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.image = image;
this.bDrag = false;
}
function Rocket(x, y, w, h, speed, image) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.speed = speed;
this.image = image;
}
function Enemy(x, y, w, h, speed, image) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.speed = speed;
this.image = image;
}
function Explosion(x, y, w, h, sprite, image) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.sprite = sprite;
this.image = image;
}
// -------------------------------------------------------------
// get random number between X and Y
function getRand(x, y) {
return Math.floor(Math.random()*y)+x;
}
// Display Intro function
function displayIntro() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(introImage, 0, 0,700, 700);
}
// Draw Main scene function
function drawScene() {
if (! bPause) {
iBgShiftY -= 2; // move main ground
if (iBgShiftY < 5) { // Finish position
bPause = true;
// draw score
ctx.font = '40px Verdana';
ctx.fillStyle = '#fff';
ctx.fillText('Finish, your score: ' + iScore * 10 + ' points', 50, 200);
return;
}
// process pressed keys (movement of plane)
processPressedKeys();
// clear canvas
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// draw background
ctx.drawImage(backgroundImage, 0, 0 + iBgShiftY, 700, 700, 0, 0, 700, 700);
// draw plane
ctx.drawImage(plane.image, iSprPos*plane.w, 0, plane.w, plane.h, plane.x - plane.w/2, plane.y - plane.h/2, plane.w, plane.h);
// draw rockets
if (rockets.length > 0) {
for (var key in rockets) {
if (rockets[key] != undefined) {
ctx.drawImage(rockets[key].image, rockets[key].x, rockets[key].y);
rockets[key].y -= rockets[key].speed;
// if a rocket is out of screen - remove it
if (rockets[key].y < 0) {
delete rockets[key];
}
}
}
}
// draw explosions
if (explosions.length > 0) {
for (var key in explosions) {
if (explosions[key] != undefined) {
// display explosion sprites
ctx.drawImage(explosions[key].image, explosions[key].sprite*explosions[key].w, 0, explosions[key].w, explosions[key].h, explosions[key].x - explosions[key].w/2, explosions[key].y - explosions[key].h/2, explosions[key].w, explosions[key].h);
explosions[key].sprite++;
// remove an explosion object when it expires
if (explosions[key].sprite > 10) {
delete explosions[key];
}
}
}
}
// draw enemies
if (enemies.length > 0) {
for (var ekey in enemies) {
if (enemies[ekey] != undefined) {
ctx.drawImage(enemies[ekey].image, enemies[ekey].x, enemies[ekey].y);
enemies[ekey].y -= enemies[ekey].speed;
// remove an enemy object if it is out of screen
if (enemies[ekey].y > canvas.height) {
delete enemies[ekey];
}
}
}
}
if (enemies.length > 0) {
for (var ekey in enemies) {
if (enemies[ekey] != undefined) {
// collisions with rockets
if (rockets.length > 0) {
for (var key in rockets) {
if (rockets[key] != undefined) {
if (rockets[key].y < enemies[ekey].y + enemies[ekey].h/2 && rockets[key].x > enemies[ekey].x && rockets[key].x + rockets[key].w < enemies[ekey].x + enemies[ekey].w) {
explosions.push(new Explosion(enemies[ekey].x + enemies[ekey].w / 2, enemies[ekey].y + enemies[ekey].h / 2, 120, 120, 0, oExplosionImage));
// delete enemy, rocket, and add +1 to score
delete enemies[ekey];
delete rockets[key];
iScore++;
}
}
}
}
// collisions with plane
if (enemies[ekey] != undefined) {
if (plane.y - plane.h/2 < enemies[ekey].y + enemies[ekey].h/2 && plane.x - plane.w/2 < enemies[ekey].x + enemies[ekey].w && plane.x + plane.w/2 > enemies[ekey].x) {
explosions.push(new Explosion(enemies[ekey].x + enemies[ekey].w / 2, enemies[ekey].y + enemies[ekey].h / 2, 120, 120, 0, oExplosionImage));
// delete enemy and make damage
delete enemies[ekey];
iLife -= iDamage;
if (iLife <= 0) { // Game over
bPause = true;
// draw score
ctx.font = '38px Verdana';
ctx.fillStyle = '#fff';
ctx.fillText('Game over, your score: ' + iScore * 10 + ' points', 25, 200);
return;
}
}
}
}
}
}
// display life and score
ctx.font = '14px Verdana';
ctx.fillStyle = '#fff';
ctx.fillText('Life: ' + iLife + ' / 100', 50, 660);
ctx.fillText('Score: ' + iScore * 10, 50, 680);
}
}
// Process Pressed Keys function
function processPressedKeys() {
if (pressedKeys[37] != undefined) { // 'Left' key
if (iSprPos > 0) {
iSprPos--;
iMoveDir = -7;
}
if (plane.x - plane.w / 2 > 10) {
plane.x += iMoveDir;
}
}
else if (pressedKeys[39] != undefined) { // 'Right' key
if (iSprPos < 4) {
iSprPos++;
iMoveDir = 7;
}
if (plane.x + plane.w / 2 < canvas.width - 10) {
plane.x += iMoveDir;
}
}
}
// Add Enemy function (adds a new enemy randomly)
function addEnemy() {
clearInterval(enTimer);
var randX = getRand(0, canvas.height - iEnemyH);
enemies.push(new Enemy(randX, 0, iEnemyW, iEnemyH, - iEnemySpeed, oEnemyImage));
var interval = getRand(1000, 4000);
enTimer = setInterval(addEnemy, interval); // loop
}
// Main Initialization
$(function(){
canvas = document.getElementById('scene');
ctx = canvas.getContext('2d');
// load background image
backgroundImage = new Image();
backgroundImage.src = 'images/levelmap.jpg';
backgroundImage.onload = function() {
}
backgroundImage.onerror = function() {
console.log('Error loading the background image.');
}
introImage = new Image();
introImage.src = 'images/intro.jpg';
// initialization of empty rocket
oRocketImage = new Image();
oRocketImage.src = 'images/rocket.png';
oRocketImage.onload = function() { }
// initialization of explosion image
oExplosionImage = new Image();
oExplosionImage.src = 'images/explosion.png';
oExplosionImage.onload = function() { }
// initialization of empty enemy
oEnemyImage = new Image();
oEnemyImage.src = 'images/enemy.png';
oEnemyImage.onload = function() { }
// initialization of plane
var oPlaneImage = new Image();
oPlaneImage.src = 'images/plane.png';
oPlaneImage.onload = function() {
plane = new Plane(canvas.width / 2, canvas.height - 100, planeW, planeH, oPlaneImage);
}
$(window).keydown(function (evt){ // onkeydown event handle
var pk = pressedKeys[evt.keyCode];
if (! pk) {
pressedKeys[evt.keyCode] = 1; // add all pressed keys into array
}
if (bPause && evt.keyCode == 13) { // in case of Enter button
bPause = false;
// start main animation
setInterval(drawScene, 30); // loop drawScene
// and add first enemy
addEnemy();
}
});
$(window).keyup(function (evt) { // onkeyup event handle
var pk = pressedKeys[evt.keyCode];
if (pk) {
delete pressedKeys[evt.keyCode]; // remove pressed key from array
}
if (evt.keyCode == 65) { // 'A' button - add a rocket
rockets.push(new Rocket(plane.x - 16, plane.y - plane.h, 32, 32, iRocketSpeed, oRocketImage));
}
if (evt.keyCode == 37 || evt.keyCode == 39) {
// revert plane sprite to default position
if (iSprPos > 2) {
for (var i = iSprPos; i >= 2; i--) {
iSprPos = i;
iMoveDir = 0;
}
} else {
for (var i = iSprPos; i <= 2; i++) {
iSprPos = i;
iMoveDir = 0;
}
}
}
});
// when intro is ready - display it
introImage.onload = function() {
displayIntro(); // Display intro once
}
});
In the main initialization the script loads all necessary images (level map, intro screen, rocket, explosion sprite, enemy and plane sprite). Then, in order to handle with multiple pressed keys we have to use Array (pressedKeys) to keep all pressed keys (then, during rendering of the main scene, we will use this array to manipulate with our plane), and finally, once the Intro page is loaded – we display intro screen. One of important moments – handling of multiple pressed keys, look at this code:
var pressedKeys = []; // array of pressed keys
$(window).keydown(function (evt){ // onkeydown event handle
var pk = pressedKeys[evt.keyCode];
if (! pk) {
pressedKeys[evt.keyCode] = 1; // add all pressed keys into array
}
});
$(window).keyup(function (evt) { // onkeyup event handle
var pk = pressedKeys[evt.keyCode];
if (pk) {
delete pressedKeys[evt.keyCode]; // remove pressed key from array
}
});This technique allows us to handle multiple keys. Well, during rendering of the main scene we draw next objects: level background, the main plane, rockets (of our plane), enemies and explosions. Once we hit an enemy – we draw explosion sprite at the last place of the enemy. And finally, our opponents are not harmless, as soon as they touch our plane – they explode and cause damage to our aircraft. And, if our life value is under zero – game over. To implement collisions and explosions, I used the following code:
if (plane.y - plane.h/2 < enemies[ekey].y + enemies[ekey].h/2 && plane.x - plane.w/2 < enemies[ekey].x + enemies[ekey].w && plane.x + plane.w/2 > enemies[ekey].x) {
explosions.push(new Explosion(enemies[ekey].x + enemies[ekey].w / 2, enemies[ekey].y + enemies[ekey].h / 2, 120, 120, 0, oExplosionImage));
// delete enemy and make damage
delete enemies[ekey];
iLife -= iDamage;
if (iLife <= 0) { // Game over
bPause = true;
// draw score
ctx.font = '38px Verdana';
ctx.fillStyle = '#fff';
ctx.fillText('Game over, your score: ' + iScore * 10 + ' points', 25, 200);
return;
}
}Step 3. Custom graphics
enemy.png, explosion.png, intro.jpg, levelmap.jpg, plane.png, rocket.png
All used images are available in our package.
Conclusion
Do you like the new SkyWalker game?
I will be glad to see your comments. Good luck!
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)





