HTML5 Game Development – Lesson 10

Tutorials

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:

01 <!DOCTYPE html>
02 <html lang="en" >
03     <head>
04         <meta charset="utf-8" />
05         <meta name="author" content="Script Tutorials" />
06         <title>HTML5 Game Development - Lesson 10 (SkyWalker) | Script Tutorials</title>
07         <!-- add styles -->
08         <link href="css/main.css" rel="stylesheet" type="text/css" />
09         <!-- add scripts -->
10         <script src="js/jquery.js"></script>
11         <script src="js/script.js"></script>
12     </head>
13     <body>
14         <div class="container">
15             <canvas id="scene" width="700" height="700" tabindex="1"></canvas>
16         </div>
17     </body>
18 </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

001 // inner variables
002 var canvas, ctx;
003 // images
004 var backgroundImage;
005 var oRocketImage;
006 var oExplosionImage;
007 var introImage;
008 var oEnemyImage;
009 var iBgShiftY = 9300; //10000 (level length) - 700 (canvas height)
010 var bPause = true// game pause
011 var plane = null// plane object
012 var rockets = []; // array of rockets
013 var enemies = []; // array of enemies
014 var explosions = []; // array of explosions
015 var planeW = 200; // plane width
016 var planeH = 110; // plane height
017 var iSprPos = 2; // initial sprite frame for plane
018 var iMoveDir = 0; // move direction
019 var iEnemyW = 128; // enemy width
020 var iEnemyH = 128; // enemy height
021 var iRocketSpeed = 10; // initial rocket speed
022 var iEnemySpeed = 5; // initial enemy speed
023 var pressedKeys = []; // array of pressed keys
024 var iScore = 0; // total score
025 var iLife = 100; // total life of plane
026 var iDamage = 10; // damage per enemy plane
027 var enTimer = null// random timer for a new enemy
028 // -------------------------------------------------------------
029 // objects :
030 function Plane(x, y, w, h, image) {
031     this.x = x;
032     this.y = y;
033     this.w = w;
034     this.h = h;
035     this.image = image;
036     this.bDrag = false;
037 }
038 function Rocket(x, y, w, h, speed, image) {
039     this.x = x;
040     this.y = y;
041     this.w = w;
042     this.h = h;
043     this.speed = speed;
044     this.image = image;
045 }
046 function Enemy(x, y, w, h, speed, image) {
047     this.x = x;
048     this.y = y;
049     this.w = w;
050     this.h = h;
051     this.speed = speed;
052     this.image = image;
053 }
054 function Explosion(x, y, w, h, sprite, image) {
055     this.x = x;
056     this.y = y;
057     this.w = w;
058     this.h = h;
059     this.sprite = sprite;
060     this.image = image;
061 }
062 // -------------------------------------------------------------
063 // get random number between X and Y
064 function getRand(x, y) {
065     return Math.floor(Math.random()*y)+x;
066 }
067 // Display Intro function
068 function displayIntro() {
069     ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
070     ctx.drawImage(introImage, 0, 0,700, 700);
071 }
072 // Draw Main scene function
073 function drawScene() {
074     if (! bPause) {
075         iBgShiftY -= 2; // move main ground
076         if (iBgShiftY < 5) { // Finish position
077             bPause = true;
078             // draw score
079             ctx.font = '40px Verdana';
080             ctx.fillStyle = '#fff';
081             ctx.fillText('Finish, your score: ' + iScore * 10 + ' points', 50, 200);
082             return;
083         }
084         // process pressed keys (movement of plane)
085         processPressedKeys();
086         // clear canvas
087         ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
088         // draw background
089         ctx.drawImage(backgroundImage, 0, 0 + iBgShiftY, 700, 700, 0, 0, 700, 700);
090         // draw plane
091         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);
092         // draw rockets
093         if (rockets.length > 0) {
094             for (var key in rockets) {
095                 if (rockets[key] != undefined) {
096                     ctx.drawImage(rockets[key].image, rockets[key].x, rockets[key].y);
097                     rockets[key].y -= rockets[key].speed;
098                     // if a rocket is out of screen - remove it
099                     if (rockets[key].y < 0) {
100                         delete rockets[key];
101                     }
102                 }
103             }
104         }
105         // draw explosions
106         if (explosions.length > 0) {
107             for (var key in explosions) {
108                 if (explosions[key] != undefined) {
109                     // display explosion sprites
110                     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);
111                     explosions[key].sprite++;
112                     // remove an explosion object when it expires
113                     if (explosions[key].sprite > 10) {
114                         delete explosions[key];
115                     }
116                 }
117             }
118         }
119         // draw enemies
120         if (enemies.length > 0) {
121             for (var ekey in enemies) {
122                 if (enemies[ekey] != undefined) {
123                     ctx.drawImage(enemies[ekey].image, enemies[ekey].x, enemies[ekey].y);
124                     enemies[ekey].y -= enemies[ekey].speed;
125                     // remove an enemy object if it is out of screen
126                     if (enemies[ekey].y > canvas.height) {
127                         delete enemies[ekey];
128                     }
129                 }
130             }
131         }
132         if (enemies.length > 0) {
133             for (var ekey in enemies) {
134                 if (enemies[ekey] != undefined) {
135                     // collisions with rockets
136                     if (rockets.length > 0) {
137                         for (var key in rockets) {
138                             if (rockets[key] != undefined) {
139                                 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) {
140                                     explosions.push(new Explosion(enemies[ekey].x + enemies[ekey].w / 2, enemies[ekey].y + enemies[ekey].h / 2, 120, 120, 0, oExplosionImage));
141                                     // delete enemy, rocket, and add +1 to score
142                                     delete enemies[ekey];
143                                     delete rockets[key];
144                                     iScore++;
145                                 }
146                             }
147                         }
148                     }
149                     // collisions with plane
150                     if (enemies[ekey] != undefined) {
151                         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) {
152                             explosions.push(new Explosion(enemies[ekey].x + enemies[ekey].w / 2, enemies[ekey].y + enemies[ekey].h / 2, 120, 120, 0, oExplosionImage));
153                             // delete enemy and make damage
154                             delete enemies[ekey];
155                             iLife -= iDamage;
156                             if (iLife <= 0) { // Game over
157                                 bPause = true;
158                                 // draw score
159                                 ctx.font = '38px Verdana';
160                                 ctx.fillStyle = '#fff';
161                                 ctx.fillText('Game over, your score: ' + iScore * 10 + ' points', 25, 200);
162                                 return;
163                             }
164                         }
165                     }
166                 }
167             }
168         }
169         // display life and score
170         ctx.font = '14px Verdana';
171         ctx.fillStyle = '#fff';
172         ctx.fillText('Life: ' + iLife + ' / 100', 50, 660);
173         ctx.fillText('Score: ' + iScore * 10, 50, 680);
174     }
175 }
176 // Process Pressed Keys function
177 function processPressedKeys() {
178     if (pressedKeys[37] != undefined) { // 'Left' key
179         if (iSprPos > 0) {
180             iSprPos--;
181             iMoveDir = -7;
182         }
183         if (plane.x - plane.w / 2 > 10) {
184             plane.x += iMoveDir;
185         }
186     }
187     else if (pressedKeys[39] != undefined) { // 'Right' key
188         if (iSprPos < 4) {
189             iSprPos++;
190             iMoveDir = 7;
191         }
192         if (plane.x + plane.w / 2 < canvas.width - 10) {
193             plane.x += iMoveDir;
194         }
195     }
196 }
197 // Add Enemy function (adds a new enemy randomly)
198 function addEnemy() {
199     clearInterval(enTimer);
200     var randX = getRand(0, canvas.height - iEnemyH);
201     enemies.push(new Enemy(randX, 0, iEnemyW, iEnemyH, - iEnemySpeed, oEnemyImage));
202     var interval = getRand(1000, 4000);
203     enTimer = setInterval(addEnemy, interval); // loop
204 }
205 // Main Initialization
206 $(function(){
207     canvas = document.getElementById('scene');
208     ctx = canvas.getContext('2d');
209     // load background image
210     backgroundImage = new Image();
211     backgroundImage.src = 'images/levelmap.jpg';
212     backgroundImage.onload = function() {
213     }
214     backgroundImage.onerror = function() {
215         console.log('Error loading the background image.');
216     }
217     introImage = new Image();
218     introImage.src = 'images/intro.jpg';
219     // initialization of empty rocket
220     oRocketImage = new Image();
221     oRocketImage.src = 'images/rocket.png';
222     oRocketImage.onload = function() { }
223     // initialization of explosion image
224     oExplosionImage = new Image();
225     oExplosionImage.src = 'images/explosion.png';
226     oExplosionImage.onload = function() { }
227     // initialization of empty enemy
228     oEnemyImage = new Image();
229     oEnemyImage.src = 'images/enemy.png';
230     oEnemyImage.onload = function() { }
231     // initialization of plane
232     var oPlaneImage = new Image();
233     oPlaneImage.src = 'images/plane.png';
234     oPlaneImage.onload = function() {
235         plane = new Plane(canvas.width / 2, canvas.height - 100, planeW, planeH, oPlaneImage);
236     }
237     $(window).keydown(function (evt){ // onkeydown event handle
238         var pk = pressedKeys[evt.keyCode];
239         if (! pk) {
240             pressedKeys[evt.keyCode] = 1; // add all pressed keys into array
241         }
242         if (bPause && evt.keyCode == 13) { // in case of Enter button
243             bPause = false;
244             // start main animation
245             setInterval(drawScene, 30); // loop drawScene
246             // and add first enemy
247             addEnemy();
248         }
249     });
250     $(window).keyup(function (evt) { // onkeyup event handle
251         var pk = pressedKeys[evt.keyCode];
252         if (pk) {
253             delete pressedKeys[evt.keyCode]; // remove pressed key from array
254         }
255         if (evt.keyCode == 65) { // 'A' button - add a rocket
256             rockets.push(new Rocket(plane.x - 16, plane.y - plane.h, 32, 32, iRocketSpeed, oRocketImage));
257         }
258         if (evt.keyCode == 37 || evt.keyCode == 39) {
259             // revert plane sprite to default position
260             if (iSprPos > 2) {
261                 for (var i = iSprPos; i >= 2; i--) {
262                     iSprPos = i;
263                     iMoveDir = 0;
264                 }
265             else {
266                 for (var i = iSprPos; i <= 2; i++) {
267                     iSprPos = i;
268                     iMoveDir = 0;
269                 }
270             }
271         }
272     });
273     // when intro is ready - display it
274     introImage.onload = function() {
275         displayIntro(); // Display intro once
276     }
277 });

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:

01 var pressedKeys = []; // array of pressed keys
02 $(window).keydown(function (evt){ // onkeydown event handle
03     var pk = pressedKeys[evt.keyCode];
04     if (! pk) {
05         pressedKeys[evt.keyCode] = 1; // add all pressed keys into array
06     }
07 });
08 $(window).keyup(function (evt) { // onkeyup event handle
09     var pk = pressedKeys[evt.keyCode];
10     if (pk) {
11         delete pressedKeys[evt.keyCode]; // remove pressed key from array
12     }
13 });

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:

01 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) {
02     explosions.push(new Explosion(enemies[ekey].x + enemies[ekey].w / 2, enemies[ekey].y + enemies[ekey].h / 2, 120, 120, 0, oExplosionImage));
03     // delete enemy and make damage
04     delete enemies[ekey];
05     iLife -= iDamage;
06     if (iLife <= 0) { // Game over
07         bPause = true;
08         // draw score
09         ctx.font = '38px Verdana';
10         ctx.fillStyle = '#fff';
11         ctx.fillText('Game over, your score: ' + iScore * 10 + ' points', 25, 200);
12         return;
13     }
14 }

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!

Rate article