原文地址:http://www.script-tutorials.com/html5-game-development-lesson-7/
今天我们将完成我们第一个完整的游戏--打砖块。这次教程中,将展示怎样进行基本的碰撞检测和使用HTML5的本地存储。你可以使用鼠标和键盘来操作挡板,上一次游戏的持续时间和分数将会保存。
前一篇的的介绍在HTML5游戏开发系列教程6(译)。
第一步:HTML
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="utf-8" /> 5 <title>HTML5 Game Development - Lesson 7 | Script Tutorials</title> 6 <link href="css/main.css" rel="stylesheet" type="text/css" /> 7 <script src="js/jquery-2.0.0.min.js"></script> 8 <script src="js/script.js"></script> 9 </head> 10 <body> 11 <header> 12 <h2>HTML5 Game Development - Lesson 7</h2> 13 <a href="http://www.script-tutorials.com/html5-game-development-lesson-7/" class="stuts">Back to original tutorial on <span>Script Tutorials</span></a> 14 </header> 15 <div class="container"> 16 <canvas id="scene" width="800" height="600"></canvas> 17 </div> 18 </body> 19 </html>
第二步:CSS
下面是css样式文件
css/main.css
1 /* page layout styles */ 2 *{ 3 margin:0; 4 padding:0; 5 } 6 body { 7 background-color:#eee; 8 color:#fff; 9 font:14px/1.3 Arial,sans-serif; 10 } 11 header { 12 background-color:#212121; 13 box-shadow: 0 -1px 2px #111111; 14 display:block; 15 height:70px; 16 position:relative; 17 width:100%; 18 z-index:100; 19 } 20 header h2{ 21 font-size:22px; 22 font-weight:normal; 23 left:50%; 24 margin-left:-400px; 25 padding:22px 0; 26 position:absolute; 27 width:540px; 28 } 29 header a.stuts,a.stuts:visited{ 30 border:none; 31 text-decoration:none; 32 color:#fcfcfc; 33 font-size:14px; 34 left:50%; 35 line-height:31px; 36 margin:23px 0 0 110px; 37 position:absolute; 38 top:0; 39 } 40 header .stuts span { 41 font-size:22px; 42 font-weight:bold; 43 margin-left:5px; 44 } 45 .container { 46 margin: 20px auto; 47 overflow: hidden; 48 position: relative; 49 width: 800px; 50 }
第三步:JS
js/jquery-2.0.0.min.js (原文使用的是jquery-1.5.2.min.js)
js/script.js
1 //内部变量 2 var canvas, ctx; 3 4 var iStart = 0; 5 var bRightBut = false; 6 var bLeftBut = false; 7 var oBall, oPadd, oBricks; 8 var aSounds = []; 9 var iPoints = 0; 10 var iGameTimer; 11 var iElapsed = iMin = iSec = 0; 12 var sLastTime, sLastPoints; 13 14 /** 15 * @brief 球体对象 16 * 17 * @param x 横坐标 18 * @param y 纵坐标 19 * @param dx 横坐标将要移动的距离 20 * @param dy 纵坐标将要移动的距离 21 * @param r 半径 22 * 23 * @return 24 */ 25 function Ball(x, y, dx, dy, r) { 26 this.x = x; 27 this.y = y; 28 this.dx = dx; 29 this.dy = dy; 30 this.r = r; 31 } 32 33 /** 34 * @brief 挡板对象 35 * 36 * @param x 横坐标--纵坐标固定的 37 * @param w 宽端 38 * @param h 高度 39 * @param img 图片 40 * 41 * @return 42 */ 43 function Padd(x, w, h, img) { 44 this.x = x; 45 this.w = w; 46 this.h = h; 47 this.img = img; 48 } 49 50 /** 51 * @brief 砖块对象 52 * 53 * @param w 宽度 54 * @param h 高度 55 * @param r row 第几排 56 * @param c column 第几列 57 * @param p 砖块之间的间隙 58 * 59 * @return 60 */ 61 function Bricks(w, h, r, c, p) { 62 this.w = w; 63 this.h = h; 64 this.r = r; 65 this.c = c; 66 this.p = p; 67 this.objs; 68 this.colors = ['#9d9d9d', '#f80207', '#feff01', '#0072ff', '#fc01fc', '#03fe03']; 69 } 70 71 function clear() { 72 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 73 74 ctx.fillStyle = '#111'; 75 ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); 76 } 77 78 function drawScene() { 79 clear(); 80 81 //绘制球 82 ctx.fillStyle = '#f66'; 83 ctx.beginPath(); 84 ctx.arc(oBall.x, oBall.y, oBall.r, 0, Math.PI * 2, true); 85 ctx.closePath(); 86 ctx.fill(); 87 88 //padd左右移动 89 if (bRightBut) { 90 oPadd.x += 5; 91 } else if (bLeftBut) { 92 oPadd.x -= 5; 93 } 94 95 ctx.drawImage(oPadd.img, oPadd.x, ctx.canvas.height - oPadd.h); 96 97 //绘制砖块 98 for (i = 0; i < oBricks.r; i++) { 99 ctx.fillStyle = oBricks.colors[i]; 100 for (j = 0; j < oBricks.c; j++) { 101 if (oBricks.objs[i][j] == 1) { 102 ctx.beginPath(); 103 ctx.rect((j * (oBricks.w + oBricks.p)) + oBricks.p, (i * (oBricks.h + oBricks.p)) + oBricks.p, oBricks.w, oBricks.h); 104 ctx.closePath(); 105 ctx.fill(); 106 } 107 } 108 } 109 110 //处理碰撞检测 111 iRowH = oBricks.h + oBricks.p; 112 iRow = Math.floor(oBall.y / iRowH); 113 iCol = Math.floor(oBall.x / (oBricks.w + oBricks.p)); 114 115 if (oBall.y < oBricks.r * iRowH && iRow >= 0 && iCol >= 0 && oBricks.objs[iRow][iCol] == 1) { //处理球碰到砖块 116 oBricks.objs[iRow][iCol] = 0; 117 oBall.dy = -oBall.dy; 118 iPoints++; 119 120 aSounds[0].play(); 121 } 122 123 if (oBall.x + oBall.dx + oBall.r > ctx.canvas.width || oBall.x + oBall.dx - oBall.r < 0) { //处理左右边界 124 oBall.dx = -oBall.dx; 125 } 126 127 if (oBall.y + oBall.dy - oBall.r < 0) { //处理上边界 128 oBall.dy = -oBall.dy; 129 } else if (oBall.y + oBall.dy + oBall.r > ctx.canvas.height - oPadd.h) { //处理下边界 130 if (oBall.x > oPadd.x && oBall.x < oPadd.x + oPadd.w) { //球碰到挡板反弹 131 oBall.dx = 10 * ((oBall.x - (oPadd.x + oPadd.w / 2)) / oPadd.w); 132 oBall.dy = -oBall.dy; 133 134 aSounds[2].play(); 135 } else if (oBall.y + oBall.dy + oBall.r > ctx.canvas.height) { //失败 136 clearInterval(iStart); 137 clearInterval(iGameTimer); 138 139 //在Local Storage中存持续时间和分数 140 localStorage.setItem('last-time', iMin + ':' + iSec); 141 localStorage.setItem('last-points', iPoints); 142 143 aSounds[1].play(); 144 } 145 } 146 147 oBall.x += oBall.dx; 148 oBall.y += oBall.dy; 149 150 //显示分数和时间 151 ctx.font = '16px Verdana'; 152 ctx.fillStyle = '#fff'; 153 iMin = Math.floor(iElapsed / 60); 154 iSec = iElapsed % 60; 155 if (iMin < 10) { 156 iMin = "0" + iMin; 157 } 158 if (iSec < 10) { 159 iSec = "0" + iSec; 160 } 161 ctx.fillText('Time: ' + iMin + ':' + iSec, 600, 520); 162 ctx.fillText('Points: ' + iPoints, 600, 550); 163 164 if (sLastTime != null && sLastPoints != null) { 165 ctx.fillText('Last Time: ' + sLastTime, 600, 400); 166 ctx.fillText('Last Points: ' + sLastPoints, 600, 490); 167 } 168 } 169 170 //初始化 171 $(function() { 172 canvas = document.getElementById('scene'); 173 ctx = canvas.getContext('2d'); 174 175 var width = canvas.width; 176 var height = canvas.height; 177 178 var padImg = new Image(); 179 padImg.src = 'images/padd.png'; 180 padImg.onload = function() {}; 181 182 oBall = new Ball(width / 2, 550, 0.5, -5, 10); 183 oPadd = new Padd(width / 2, 120, 20, padImg); 184 oBricks = new Bricks((width / 8) - 1, 20, 6, 8, 2); 185 186 oBricks.objs = new Array(oBricks.r); //oBricks.objs 是个二维数组 187 for (i = 0; i <oBricks.r; i++) { 188 oBricks.objs[i] = new Array(oBricks.c); 189 for (j = 0; j < oBricks.c; j++) { 190 oBricks.objs[i][j] = 1; 191 } 192 } 193 194 //声音 195 aSounds[0] = new Audio('media/snd1.wav'); 196 aSounds[0].volume = 0.9; 197 aSounds[1] = new Audio('media/snd2.wav'); 198 aSounds[1].volume = 0.9; 199 aSounds[2] = new Audio('media/snd3.wav'); 200 aSounds[2].volume = 0.9; 201 202 iStart = setInterval(drawScene, 10); //重绘 203 iGameTimer = setInterval(countTimer, 1000); //计数器 204 205 sLastTime = localStorage.getItem('last-time'); 206 sLastPoints = localStorage.getItem('last-points'); 207 208 $(window).keydown(function(event) { 209 switch (event.keyCode) { 210 case 37: 211 bLeftBut = true; 212 break; 213 case 39: 214 bRightBut = true; 215 break; 216 } 217 }); 218 $(window).keyup(function(event) { 219 switch (event.keyCode) { 220 case 37: 221 bLeftBut = false; 222 break; 223 case 39: 224 bRightBut = false; 225 break; 226 } 227 }); 228 229 //处理挡板跟随鼠标移动 230 var iCanvX1 = $(canvas).offset().left; 231 var iCanvX2 = iCanvX1 + width; 232 $('#scene').mousemove(function(e) { 233 if (e.pageX > iCanvX1 && e.pageX < iCanvX2) { 234 oPadd.x = Math.max(e.pageX - iCanvX1 - (oPadd.w / 2), 0); 235 oPadd.x = Math.min(ctx.canvas.width - oPadd.w, oPadd.x); 236 } 237 }); 238 }); 239 240 function countTimer() { 241 iElapsed++; 242 }
我在很多地方添加了注释,希望这些代码很容易理解。注意localStorage对象,并理解它在HTML5本地存储中是如果使用的(使用setItem方法来存储数据,使用getItem来取出数据)。同样,理解怎样处理球和砖块之间的碰撞检测将会很有趣。
结论:
这次我们编写了我们的第一个打砖块游戏。最重要的功能已经呈现了,并且学习了碰撞检测和HTML5的本地存储。我非常乐意看见你的谢意和评论。好运!