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. There are next key features: using sprites for the plane and explosions, possibility to press multiple keys (as 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 params.
Our previous article you can read here: Developing Your First HTML5 Game – Lesson 9.
Now you can check our demo and download the sources:
Live Demo
[sociallocker]
download in package
[/sociallocker]
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
Live Demo
Conclusion
Are you like our new SkyWalker game? :-) I will be glad to see your thanks and comments. Good luck!
Ideal. This is exactly what I have been looking for to start my boy programming. All the essential code to make any number of similar games. A big “Thank you”
Nice game tutorial.
Awsome! Complete sharing. Global community thanks to you guys!
This really very good example of HTML5 games, I am searching this type of examples from long time. Thanks a lot….
thank you for your line of HTML5 game development tutorials. Going through all of them I could learn some new tricks and realize that some of the stuff I have learned before on the mobile could be used here. It is unbelievable how simple it all can be with excellent results. Thank you very much!
Nice article on HTML5 game development.
This is a really great introduction – Thank you for your time and effort :)
Thanks for the tutorial, I just started learning css and js and you gave me an awesome start! I was wondering about the movement code, but it seems kinda easy. Once again, thanks for the great tutorial!
how to create button next if the game is finish..? thanks before
Hello Rully,
In the place where we display the result – you can add new javascript code which generates (or just displays) the necessary link
thank your acticle!!
the line 157,should be “if (rockets[key] != undefined && enemies[ekey] != undefined)”
Hi Bells,
Indeed, thank you for your contribution
This really very good example of HTML5 games, I am searching this type of examples from long time. Thanks alot…