原文地址:http://www.script-tutorials.com/html5-game-development-lesson-9/
今天我们将继续使用canvas来进行HTML5游戏开发系列的文章。这次我准备了一个新游戏,是基于第4篇的游戏,但是增加了火球,敌人和碰撞检测。故,我们的龙可以发射火球来杀死敌人,并且记录分数。这样该游戏就有更多的交互性。
之前的翻译文章可以点击这里:http://www.cnblogs.com/pigzhu/p/3234255.html
第一步:HTML
首先是我们基础的html代码:
1 <!DOCTYPE html> 2 <html lang="en" > 3 <head> 4 <meta charset="utf-8" /> 5 <title>HTML5 Game Development - Lesson 9 | Script Tutorials</title> 6 <link href="css/main.css" rel="stylesheet" type="text/css" /> 7 8 <!--[if lt IE 9]> 9 <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> 10 <![endif]--> 11 <script src="js/jquery-2.0.0.min.js"></script> 12 <script src="js/script.js"></script> 13 </head> 14 <body> 15 <header tabindex="0"> 16 <h2>HTML5 Game Development - Lesson 9</h2> 17 <a href="http://www.script-tutorials.com/html5-game-development-lesson-9/" class="stuts">Back to original tutorial on <span>Script Tutorials</span></a> 18 </header> 19 20 <div class="container"> 21 <canvas id="scene" width="1000" height="600" tabindex="1"></canvas> 22 </div> 23 </body> 24 </html>
第二步:CSS
接着这里是CSS样式。
css/main.css
这次依然不打算显示出CSS文件的内容了,因为仅仅只是些页面布局样式。你可以在源代码包里找到该文件。
第三步:JS
js/script.js
1 // 内部变量 2 var canvas, ctx; 3 var backgroundImage; 4 var iBgShiftX = 100; 5 6 var dragon, enemy = null; // 游戏中两个主要实体对象 7 var balls = []; 8 var enemies = []; 9 10 var dragonW = 75; // 龙的宽度 11 var dragonH = 70; // 龙的高度 12 var iSprPos = 0; // 初始帧的位置 13 var iSprDir = 0; // 初始龙的朝向 14 var iEnemyW = 128; // 敌人的宽度 15 var iEnemyH = 128; // 敌人的高度 16 var iBallSpeed = 10; // 火球的速度 17 var iEnemySpeed = 2; // 敌人的速度 18 19 var dragonSound; // 龙的声音 20 var wingsSound; // 翅膀的声音 21 var explodeSound, explodeSound2; // 爆炸声音 22 var laughtSound; // 敌人从左边逃出,嘲笑的声音 23 24 var bMouseDown = false; // 鼠标是否按下 25 var iLastMouseX = 0; 26 var iLastMouseY = 0; 27 var iScore = 0; 28 // ------------------------------------------------------------- 29 30 // 龙 31 function Dragon(x, y, w, h, image) { 32 this.x = x; 33 this.y = y; 34 this.w = w; 35 this.h = h; 36 this.image = image; 37 this.bDrag = false; 38 } 39 // 球 40 function Ball(x, y, w, h, speed, image) { 41 this.x = x; 42 this.y = y; 43 this.w = w; 44 this.h = h; 45 this.speed = speed; 46 this.image = image; 47 } 48 // 敌人 49 function Enemy(x, y, w, h, speed, image) { 50 this.x = x; 51 this.y = y; 52 this.w = w; 53 this.h = h; 54 this.speed = speed; 55 this.image = image; 56 } 57 // ------------------------------------------------------------- 58 // 获得位于x和y之间的随机数。 59 function getRand(x, y) { 60 return Math.floor(Math.random() * y) + x; 61 } 62 63 // draw functions : 64 function drawScene() { // main drawScene function 65 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // 清除这个画布 66 67 // 画背景 68 iBgShiftX += 4; 69 if (iBgShiftX >= 1045) { 70 iBgShiftX = 0; 71 } 72 ctx.drawImage(backgroundImage, 0 + iBgShiftX, 0, 1000, 940, 0, 0, 1000, 600); 73 74 // 更新帧的位置 75 iSprPos++; 76 if (iSprPos >= 9) { 77 iSprPos = 0; 78 } 79 80 // 当鼠标被按下,龙朝着鼠标移动 81 if (bMouseDown) { 82 if (iLastMouseX > dragon.x) { 83 dragon.x += 5; 84 } 85 if (iLastMouseY > dragon.y) { 86 dragon.y += 5; 87 } 88 if (iLastMouseX < dragon.x) { 89 dragon.x -= 5; 90 } 91 if (iLastMouseY < dragon.y) { 92 dragon.y -= 5; 93 } 94 } 95 96 // 绘制龙 97 ctx.drawImage(dragon.image, iSprPos * dragon.w, iSprDir * dragon.h, dragon.w, dragon.h, 98 dragon.x - dragon.w / 2, dragon.y - dragon.h / 2, dragon.w, dragon.h); 99 100 // 如果有火球则绘制 101 if (balls.length > 0) { 102 for (var key in balls) { 103 if (balls[key] != undefined) { 104 ctx.drawImage(balls[key].image, balls[key].x, balls[key].y); 105 balls[key].x += balls[key].speed; 106 107 if (balls[key].x > canvas.width) { //超出屏幕,移除 108 delete balls[key]; 109 } 110 } 111 } 112 } 113 114 // 如果有敌人则绘制 115 if (enemies.length > 0) { 116 for (var ekey in enemies) { 117 if (enemies[ekey] != undefined) { 118 ctx.drawImage(enemies[ekey].image, enemies[ekey].x, enemies[ekey].y); 119 enemies[ekey].x += enemies[ekey].speed; 120 121 if (enemies[ekey].x < - iEnemyW) { //敌人没有被击中,从左边消失,并发出嘲笑声音 122 delete enemies[ekey]; 123 124 laughtSound.currentTime = 0; 125 laughtSound.play(); 126 } 127 } 128 } 129 } 130 131 // 检测是否击中敌人 132 if (balls.length > 0) { 133 for (var key in balls) { 134 if (balls[key] != undefined) { 135 136 if (enemies.length > 0) { 137 for (var ekey in enemies) { 138 if (enemies[ekey] != undefined && balls[key] != undefined) { 139 if (balls[key].x + balls[key].w > enemies[ekey].x && balls[key].y + balls[key].h > enemies[ekey].y 140 && balls[key].y < enemies[ekey].y + enemies[ekey].h) { 141 delete enemies[ekey]; 142 delete balls[key]; 143 iScore++; 144 145 explodeSound2.currentTime = 0; 146 explodeSound2.play(); 147 } 148 } 149 } 150 } 151 } 152 } 153 } 154 155 // 绘制分数 156 ctx.font = '16px Verdana'; 157 ctx.fillStyle = '#fff'; 158 ctx.fillText('Score: ' + iScore * 10, 900, 580); 159 ctx.fillText('Plese click "1" to cast fireball', 100, 580); 160 161 } 162 163 // ------------------------------------------------------------- 164 165 // 初始化 166 $(function(){ 167 canvas = document.getElementById('scene'); 168 ctx = canvas.getContext('2d'); 169 170 var width = canvas.width; 171 var height = canvas.height; 172 173 // 加载背景图片 174 backgroundImage = new Image(); 175 backgroundImage.src = 'images/hell.jpg'; 176 backgroundImage.onload = function() { 177 } 178 backgroundImage.onerror = function() { 179 console.log('Error loading the background image.'); 180 } 181 182 //初始化各种声音 183 dragonSound = new Audio('media/dragon.wav'); 184 dragonSound.volume = 0.9; 185 186 laughtSound = new Audio('media/laught.wav'); 187 laughtSound.volume = 0.9; 188 189 explodeSound = new Audio('media/explode1.wav'); 190 explodeSound.volume = 0.9; 191 explodeSound2 = new Audio('media/explosion.wav'); 192 explodeSound2.volume = 0.9; 193 194 wingsSound = new Audio('media/wings.wav'); 195 wingsSound.volume = 0.9; 196 wingsSound.addEventListener('ended', function() { // loop wings sound 197 this.currentTime = 0; 198 this.play(); 199 }, false); 200 wingsSound.play(); 201 202 // 加载各种图片 203 var oBallImage = new Image(); 204 oBallImage.src = 'images/fireball.png'; 205 oBallImage.onload = function() { } 206 207 var oEnemyImage = new Image(); 208 oEnemyImage.src = 'images/enemy.png'; 209 oEnemyImage.onload = function() { } 210 211 var oDragonImage = new Image(); 212 oDragonImage.src = 'images/dragon.gif'; 213 oDragonImage.onload = function() { 214 dragon = new Dragon(400, 300, dragonW, dragonH, oDragonImage); 215 } 216 217 $('#scene').mousedown(function(e) { // 处理鼠标按下 218 var mouseX = e.layerX || 0; 219 var mouseY = e.layerY || 0; 220 if(e.originalEvent.layerX) { // jquery 1.7之后用下面这种方式 221 mouseX = e.originalEvent.layerX; 222 mouseY = e.originalEvent.layerY; 223 } 224 225 bMouseDown = true; 226 227 if (mouseX > dragon.x- dragon.w/2 && mouseX < dragon.x- dragon.w/2 +dragon.w && 228 mouseY > dragon.y- dragon.h/2 && mouseY < dragon.y-dragon.h/2 +dragon.h) { 229 230 dragon.bDrag = true; 231 dragon.x = mouseX; 232 dragon.y = mouseY; 233 } 234 }); 235 236 $('#scene').mousemove(function(e) { // 处理鼠标移动 237 var mouseX = e.layerX || 0; 238 var mouseY = e.layerY || 0; 239 if(e.originalEvent.layerX) { 240 mouseX = e.originalEvent.layerX; 241 mouseY = e.originalEvent.layerY; 242 } 243 244 // 保存上次鼠标的位置 245 iLastMouseX = mouseX; 246 iLastMouseY = mouseY; 247 248 // 执行龙的拖动 249 if (dragon.bDrag) { 250 dragon.x = mouseX; 251 dragon.y = mouseY; 252 } 253 254 // 根据鼠标的位置,改变龙的方向 255 if (mouseX > dragon.x && Math.abs(mouseY-dragon.y) < dragon.w/2) { 256 iSprDir = 0; 257 } else if (mouseX < dragon.x && Math.abs(mouseY-dragon.y) < dragon.w/2) { 258 iSprDir = 4; 259 } else if (mouseY > dragon.y && Math.abs(mouseX-dragon.x) < dragon.h/2) { 260 iSprDir = 2; 261 } else if (mouseY < dragon.y && Math.abs(mouseX-dragon.x) < dragon.h/2) { 262 iSprDir = 6; 263 } else if (mouseY < dragon.y && mouseX < dragon.x) { 264 iSprDir = 5; 265 } else if (mouseY < dragon.y && mouseX > dragon.x) { 266 iSprDir = 7; 267 } else if (mouseY > dragon.y && mouseX < dragon.x) { 268 iSprDir = 3; 269 } else if (mouseY > dragon.y && mouseX > dragon.x) { 270 iSprDir = 1; 271 } 272 }); 273 274 $('#scene').mouseup(function(e) { // 处理鼠标释放事件 275 dragon.bDrag = false; 276 bMouseDown = false; 277 278 // play dragon sound 279 dragonSound.currentTime = 0; 280 dragonSound.play(); 281 }); 282 283 $(window).keydown(function(event){ // 按键1,开火 284 switch (event.keyCode) { 285 case 49: // '1' key 286 balls.push(new Ball(dragon.x, dragon.y, 32, 32, iBallSpeed, oBallImage)); 287 288 // play explode sound #1 289 explodeSound.currentTime = 0; 290 explodeSound.play(); 291 break; 292 } 293 }); 294 295 setInterval(drawScene, 30); // loop drawScene 296 297 // 随机生产出敌人 298 var enTimer = null; 299 function addEnemy() { 300 clearInterval(enTimer); 301 302 var randY = getRand(0, canvas.height - iEnemyH); 303 enemies.push(new Enemy(canvas.width, randY, iEnemyW, iEnemyH, - iEnemySpeed, oEnemyImage)); 304 305 var interval = getRand(5000, 10000); 306 enTimer = setInterval(addEnemy, interval); // loop drawScene 307 } 308 addEnemy(); 309 });
在上面代码的开始处,我增加了两个新对象,球和敌人。每个对象都有他们自己的属性集(比如位置,大小,图片,速度),然后通过‘drawScene’方法来绘制他们,在该方法底部,你可以看到处理球和敌人的碰撞检测代码:
1 // 检测是否击中敌人 2 if (balls.length > 0) { 3 for (var key in balls) { 4 if (balls[key] != undefined) { 5 6 if (enemies.length > 0) { 7 for (var ekey in enemies) { 8 if (enemies[ekey] != undefined && balls[key] != undefined) { 9 if (balls[key].x + balls[key].w > enemies[ekey].x && balls[key].y + balls[key].h > enemies[ekey].y 10 && balls[key].y < enemies[ekey].y + enemies[ekey].h) { 11 delete enemies[ekey]; 12 delete balls[key]; 13 iScore++; 14 15 explodeSound2.currentTime = 0; 16 explodeSound2.play(); 17 } 18 } 19 } 20 } 21 } 22 } 23 }
最后,我们通过下面的代码不定时间的增加敌人:
1 // 随机生产出敌人 2 var enTimer = null; 3 function addEnemy() { 4 clearInterval(enTimer); 5 6 var randY = getRand(0, canvas.height - iEnemyH); 7 enemies.push(new Enemy(canvas.width, randY, iEnemyW, iEnemyH, - iEnemySpeed, oEnemyImage)); 8 9 var interval = getRand(5000, 10000); 10 enTimer = setInterval(addEnemy, interval); // loop drawScene 11 } 12 addEnemy();
第四步:Custom files
-
images/dragon.gif, images/enemy.png, images/fireball.png, images/hell.jpg
-
media/dragon.wav, media/explode1.wav, media/explosion.wav, media/laught.wav, media/wings.wav
上面所有的文件都在源码包里。