从头开始构建web前端应用——字符炸弹小游戏(一)
我有个哥们,绰号小白,做黑盒测试。这系列随笔是教他入门web前端时写的,也送给刚打算转行做前端的那些人,欢迎大神帮忙指正。
废话不多说,进入正题。
新建txt文件,改后缀名为“.html”。右击,用记事本打开(当然也可以用一些牛哄哄的IDE打开)。敲入以下字符:
<html> <head> </head> <body> </body> </html>
这就是最简单的网页结构。
现在在<head>标签和<body>标签中添加一些字符,使之变成如下样子:
<html> <head> this is a simple html </head> <body> Hello,world! </body> </html>
保存一下,然后用浏览器打开此文件,ok,最简单的网页制作完毕。
你也可以把“Hello,world!”替换成中文字符,比如“现代浏览器很强大”。
保存,再次用浏览器打开,也许你会看到一堆乱码,没关系,指定一下页面的显示字符集。一般是“UTF-8”或者“GBK ”。
在<head></head>标签中加入以下字符:
<meta charset="UTF-8" />
保存,再次用浏览器打开。ok。
现在往<body>标签里面添加一个<canvas>标签,如下面这样
<body> <canvas style="border:1px;"> </canvas> </body>
其中,style属性指明了canvas标签的边框样式。保存,再次用浏览器打开。ok。你可以看到一个框框。这块区域就是刚才添加的canvas区域。之后的绘图就是在这里进行。
为了方便后续的修改调试,建议现在把这份代码整理成下面这样:
<html> <head> <meta charset="UTF-8" /> <style type="text/css"> .showBoxStyle { border: 1px solid #000; } </style> </head> <body> <canvas id="showBox" class="showBoxStyle"> </canvas> </body> </html>
对,就是为canvas标签添加id和class属性,然后在head标签里面指明style标签写样式。
然后,就开始正式写js代码作交互了。
javascript简称js,一般在HTML文档的最后添加<script>标签进行编辑。不妨先在<body>标签结束之后写一段js试试:
<html> <head> <meta charset="UTF-8" /> <style type="text/css"> .showBoxStyle { border: 1px solid #000; } </style> </head> <body> <canvas id="showBox" class="showBoxStyle"> </canvas> </body> <script type="text/javascript"> var showBox = document.getElementById("showBox"); /*通过id属性获取界面对象canvas*/ var context = showBox.getContext('2d'); /*设置canvas的绘制属性*/ context.clearRect(0,0,300,150); context.fillStyle = "#000000"; context.fillRect(0, 0,300,150); </script> </html>
保存,在浏览器里打开,ok,现在绘制了一个全屏的黑色矩形,下面开始修改<script>标签里面的内容,首先将canvas绘图区拉大,加长:
var showBox = document.getElementById("showBox"); var context = showBox.getContext('2d'); showBox.setAttribute("width", "600px"); showBox.setAttribute("height", "400px");
然后,我们需要一个产生随机字符的函数,可以先把字符集存在一个数组或字符串中,然后产生一定范围的随机数,这个随机数代表随机字符在字符串里的位置。按照这个随机数去取对应的字符。这里我只列出26个小写字母。
var samllchars = "abcdefghijklmnopqrstuvwxyz"; /* * 产生随机整数 */ function ranInt(num){ return Math.floor(Math.random()*num); } /* * 产生随机字符 */ function ranChars() { var _num = ranInt(samllchars.length);
return samllchars[_num];
}
接下来,需要一个显示在界面上的字符对象,这个对象应该保存有字符信息,字符的位置等。
/* * 字符对象私有属性 */ function CharObj(){ var _self = this; _self.chars = null; _self.top = 0; _self.left = 0; _self.v = 0; } /* * 字符对象初始化方法 */ CharObj.prototype.init = function(){ var _self = this; _self.chars = ranChars(); _self.top = 0; _self.left = ranInt(500); _self.v = ranInt(10)+5; } /* * 字符对象移动方法 */ CharObj.prototype.move = function(){ var _self = this; _self.top += _self.v; } /* * 字符对象显示方法 */
CharObj.prototype.draw = function(){ var _self = this; context.font = 'bold 30px 幼圆'; context.fillStyle = "#F00"; context.fillText(_self.chars, _self.left, _self.top); }
js是一种弱类型语言,稍微解释下这段代码。首先写一个函数CharObj()作为类对象的构造器(类似于C++等强类型语言的构造函数),然后在这个构造器里面写一些属性(类似于强类型语言里的私有属性)。
把this指针保存在_self变量里面是我在工作中养成的习惯,因为团队的代码规范要求这样。
再然后在CharObj的原型上挂载一些方法,也可以像上面的属性一样写在构造器函数里。这样做的好处是可以避免在多次生成同一类型实例的时候都生成新的函数。
现在,需要的是一个字符对象队列,每次生成新字符的时候把字符插入这个队列,在字符被正确输入或移出屏幕时删除。每隔一段时间就遍历这个队列,依次调用每个字符的draw方法,绘制这些字符在画布上,当然,在每次遍历之前,需要先清空画布,否则,绘制的图像会有重叠,看不出动画效果。
//字符队列 var showCharObjs = []; /* * 产生随机字符并插入字符队列 */
function proNewCharObj(){
var _charObj = new CharObj(); _charObj.init(); showCharObjs.push(_charObj); } /* * 移动并显示字符队列 */ function moveAndDrawCharObj(){ context.clearRect(0,0,600,400); context.fillStyle = "#000000"; context.fillRect(0, 0,600,400); var deleList = []; for(var i=0;i<showCharObjs.length;i++){ showCharObjs[i].move(); if(showCharObjs[i].top >= 400 ){ deleList.push(showCharObjs[i].chars); } showCharObjs[i].draw(); } if(deleList){
for(i=0;i<deleList.length;i++){ deleCharObj(deleList[i]); } }
}
/* * 删除字符队列的字符 */ function deleCharObj(cobj){ var arr = [];
for(var i=0;i<showCharObjs.length;i++){ if(showCharObjs[i].chars != cobj ){
arr.push(showCharObjs[i]);
}
} showCharObjs = arr;
}
的确,把字符移出队列的方法确实有点ugly,但我确实没有想到更好的方法,还望各位看官不吝赐教。
接下来,就是对键盘事件进行响应了。
当用户对着网页按下一个键的时候,可以得到这个键的键码,然后比照键码,将对应的字符移出队列。
function getc(ewhich){ var c = ewhich; if(c>=65&&c<=90){ c = samllchars[c-65]; }
return c; } document.onkeydown = function(e){ var c = getc(e.which); deleCharObj(c); }
最后,还需要两个计时器,一个不停地向字符队列里面插入队列,一个不停地刷新画布显示:
setInterval(proNewCharObj, 1000);
setInterval(moveAndDrawCharObj, 100);
ok,到这里基本的任务就完成了。接下来,需要做一些显示上的优化。
1.修改显示的字母颜色
2.添加一些新字符,比如“0-9”个数字
3.添加计分统计部分。
修改:
1.修改字符对象函数,增加颜色属性和绘制方法:
/* * 字符对象私有属性 */ function CharObj(){ var _self = this; _self.chars = null; _self.top = 0; _self.left = 0; _self.v = 0; _self.color = null; } /* * 字符对象初始化方法 */ CharObj.prototype.init = function(){ var _self = this; _self.chars = ranChars(); _self.top = 0; _self.left = ranInt(500); _self.v = ranInt(10)+5; var r = ranInt(100)+140; var g = ranInt(100)+100; var b = ranInt(100)+50; _self.color = "rgb("+r+","+g+","+b+")"; } /* * 字符对象移动方法 */ CharObj.prototype.move = function(){ var _self = this; _self.top += _self.v; } /* * 字符对象显示方法 */ CharObj.prototype.draw = function(){ var _self = this; context.font = 'bold 30px 幼圆'; context.fillStyle = _self.color; context.fillText(_self.chars, _self.left, _self.top); }
这里考虑到背景颜色是黑色的,字符颜色不宜太暗,所以rgb的值可以设置的高一点。
2.修改随机字符串和生成随机字符的函数
var samllchars = "abcdefghijklmnopqrstuvwxyz"; var nums = "0123456789"; var allchars = samllchars + nums; /* * 产生随机整数 */ function ranInt(num){ return Math.floor(Math.random()*num); } /* * 产生随机字符 */ function ranChars() { var _num = ranInt(allchars.length); return allchars[_num]; }
3.添加页面标签,标识得失:
<body> <div> <a> points: <span id="points">0</span> </a> </div> <canvas id="showBox" class="showBox"> </canvas> <a> miss: <span id="miss">0</span> </a> </body>
然后修改按键监听部分完成得分功能:
document.onkeydown = function(e){ var c = getc(e.which); countPoint(c); } function getc(ewhich){ var c = ewhich; if(c>=48&&c<=57){ c = nums[c-48]; } else if(c>=65&&c<=90){ c = samllchars[c-65]; } return c; } function countPoint(c){ pointFlag = false; deleCharObj(c); if(pointFlag){ points++; pointSpan.innerHTML = points; } }
再修改字符超出界面时移出队列作为失分的计分
/* * 移动并显示字符队列 */ function moveAndDrawCharObj(){ context.clearRect(0,0,600,400); context.fillStyle = "#000000"; context.fillRect(0, 0,600,400); var deleList = []; for(var i=0;i<showCharObjs.length;i++){ showCharObjs[i].move(); if(showCharObjs[i].top >= 400 ){ deleList.push(showCharObjs[i].chars); } showCharObjs[i].draw(); } if(deleList){ for(i=0;i<deleList.length;i++){ deleCharObj(deleList[i]); misspoints++; } missSpan.innerHTML = misspoints; } }
最后修改移出队列的函数,以达到计分的目的(主要是修改标识符,得分和失分时都要将字符移出字符队列)
/* * 删除字符队列的字符 */ function deleCharObj(cobj){ var arr = []; for(var i=0;i<showCharObjs.length;i++){ if(showCharObjs[i].chars != cobj ){ arr.push(showCharObjs[i]); } else{ pointFlag = true; } } showCharObjs = arr; }
最后,完整的代码如下所示。
<html> <head> <style type="text/css"> .showBox { border: 1px solid #000; } </style> </head> <body> <div> <a> points: <span id="points">0</span> </a> </div> <canvas id="showBox" class="showBox"> </canvas> <br /> <a> miss: <span id="miss">0</span> </a> <script type="text/javascript"> var samllchars = "abcdefghijklmnopqrstuvwxyz"; var nums = "0123456789"; var allchars = samllchars + nums; var showBox = document.getElementById("showBox"); var context = showBox.getContext('2d'); /* * 产生随机整数 */ function ranInt(num){ return Math.floor(Math.random()*num); } /* * 产生随机字符 */ function ranChars() { var _num = ranInt(allchars.length); return allchars[_num]; } /* * 初始化画布 */ function initCanvas(){ showBox.setAttribute("width","600px"); showBox.setAttribute("height","400px"); } initCanvas(); //得分的界面显示区域 var pointSpan = document.getElementById("points"); //失分的界面显示区域 var missSpan = document.getElementById("miss"); //字符队列 var showCharObjs = []; //得分 var points = 0; //得分标记 var pointFlag = false; var misspoints = 0; /* * 字符对象私有属性 */ function CharObj(){ var _self = this; _self.chars = null; _self.top = 0; _self.left = 0; _self.v = 0; _self.color = null; } /* * 字符对象初始化方法 */ CharObj.prototype.init = function(){ var _self = this; _self.chars = ranChars(); _self.top = 0; _self.left = ranInt(500); _self.v = ranInt(10)+5; var r = ranInt(100)+140; var g = ranInt(100)+100; var b = ranInt(100)+50; _self.color = "rgb("+r+","+g+","+b+")"; } /* * 字符对象移动方法 */ CharObj.prototype.move = function(){ var _self = this; _self.top += _self.v; } /* * 字符对象显示方法 */ CharObj.prototype.draw = function(){ var _self = this; context.font = 'bold 30px 幼圆'; context.fillStyle = _self.color; context.fillText(_self.chars, _self.left, _self.top); } /* * 产生随机字符并插入字符队列 */ function proNewCharObj(){ var _charObj = new CharObj(); _charObj.init(); showCharObjs.push(_charObj); } /* * 移动并显示字符队列 */ function moveAndDrawCharObj(){ context.clearRect(0,0,600,400); context.fillStyle = "#000000"; context.fillRect(0, 0,600,400); var deleList = []; for(var i=0;i<showCharObjs.length;i++){ showCharObjs[i].move(); if(showCharObjs[i].top >= 400 ){ deleList.push(showCharObjs[i].chars); } showCharObjs[i].draw(); } if(deleList){ for(i=0;i<deleList.length;i++){ deleCharObj(deleList[i]); misspoints++; } missSpan.innerHTML = misspoints; } } /* * 删除字符队列的字符 */ function deleCharObj(cobj){ var arr = []; for(var i=0;i<showCharObjs.length;i++){ if(showCharObjs[i].chars != cobj ){ arr.push(showCharObjs[i]); } else{ pointFlag = true; } } showCharObjs = arr; } function getc(ewhich){ var c = ewhich; if(c>=48&&c<=57){ c = nums[c-48]; } else if(c>=65&&c<=90){ c = samllchars[c-65]; } return c; } function countPoint(c){ pointFlag = false; deleCharObj(c); if(pointFlag){ points++; pointSpan.innerHTML = points; } } setInterval(proNewCharObj,1000); setInterval(moveAndDrawCharObj,100); document.onkeydown = function(e){ var c = getc(e.which); countPoint(c); } </script> </body> </html>
现在,面临的问题是:
1.代码过长,需要拆分以方便后续维护
2.字符下落速度和字符产生时间间隔可以做成配置项,并在界面上添加游戏难度选择功能,通过不同的选择,决定这些参数。
3.当字符队列为空时,字符队列的length不可取,程序运行可能还会出现bug。
这些都会后续解决。
我觉得对于初学者,能看到这里的人,我觉得需要掌握:
1.基本的网页DOM结构和语义化的DOM标签
2.知道怎么设置CSS样式。不需要知道CSS2所有的属性值,但对样式表有一点点初步的了解
3.知道怎么用js去操作DOM,以及js的函数调用方式和构造器调用方式
4.知道怎么用js去响应事件,对事件驱动模型有初步的认识。
5.对HTML5的<canvas>标签有最直接的接触。
这些是web前端中最基础也是最简单的部分。