中国领先的IT技术网站
|
|

一个HTML 5 躲避游戏的实现

HTML5写游戏和传统的游戏思路完全一样,同样也是不停刷新屏幕,游戏实际上也就是图片的适时摆放问题,HTML5无非就只用到了一个canvas(画布)的性质用来摆放图片。

作者:Billyellow's来源:Billyellow's|2012-09-24 11:11

Tech Neo技术沙龙 | 11月25号,九州云/ZStack与您一起探讨云时代网络边界管理实践


前段时间BrowserQuest激起了我对html5的乐趣,接下来记下一个小型html5躲避游戏的实现。

先上图

QQ20120921 3

游戏很简单,键盘控制人物上下左右移动,躲开怪物,时间越长越牛x。

主要是两部分组成:一部分就是人物、地图的结构搭建,另一部分就是让英雄、怪物相应地动起来。

HTML5写游戏和传统的游戏思路完全一样,同样也是不停刷新屏幕,游戏实际上也就是图片的适时摆放问题,HTML5无非就只用到了一个canvas(画布)的性质用来摆放图片。

Step 1 做好准备

新建一个html文件,命名为index.html,用作游戏的容器。代码如下:

  1. <html>   
  2. <head>   
  3. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />   
  4. <title>html5 game</title>   
  5. </head>   
  6. <body>   
  7. <h1>html5 game</h1>   
  8. <script type="text/javascript" src="move.js"></script>   
  9. </body>   
  10. </html> 

ps:script的引用最好放在body里放在body会有问题。

再新建个文件,move.js

  1. var canvas = document.createElement("canvas"); //创建元素canvas,即我们要用的画布   
  2. var ctx = canvas.getContext("2d");//说明我们要用的画布是2d,因为canvas也有WebGL支持3d   
  3. canvas.width = 512;//设置画布的长宽   
  4. canvas.height = 480;   
  5. document.body.appendChild(canvas);//前面基本信息都设置好了之后,将这个元素添加到body标签下。 

这样画布就算是搭建好了。

顺带在下面加几个和图片有关的函数。

  1. var bgReady = false;   
  2. var bgImage = new Image();   
  3. bgImage.src = "move/background.png";   
  4. bgImage.onload = function(){   
  5. bgReady = true;   
  6. }   
  7.     
  8. var heroReady = false;   
  9. var heroImage = new Image();   
  10. heroImage.src = "move/hero.png";   
  11. heroImage.onload = function(){   
  12. heroReady = true;   
  13. }   
  14.     
  15. var monsterReady = false;   
  16. var monsterImage = new Image();   
  17. monsterImage.src = "move/monster.png";   
  18. monsterImage.onload = function(){   
  19. monsterReady = true;   

这个游戏用了三张图片,依次为背景、英雄、怪物。这段代码很容易理解,为了不在图片没有加载完成的时候就draw图片。

Step 2 定义原型

接下来定义一下英雄的原型。

  1. var hero = {   
  2. speed: 256,   
  3. x: canvas.width/2,   
  4. y: canvas.height/2   

这个原型也很好理解,每秒钟英雄可以移动256个像素,英雄初始的位置为画布中央(x,y分别为坐标)。

接下来轮到怪物了。: )

  1. function monster() {   
  2. this.x = Math.random() * canvas.width;//初始为止随机   
  3. this.y = Math.random() * canvas.height;   
  4. this.speed = 100;   
  5. this.xDirection = 1;//默认移动方向为x轴正方向(以左上角为零点,下方和右方为正)   
  6. this.yDirection = 1;//同样也为y轴正方向   
  7. this.move = function (modifier) {//移动函数   
  8. this.x += this.xDirection * this.speed * modifier;   
  9. this.y += this.yDirection * this.speed * modifier;   
  10. if (this.x >= canvas.width - 32)//碰撞返回部分   
  11. {   
  12. this.x = canvas.width - 32;   
  13. this.xDirection = -1;   
  14. }else if (this.x <= 0)   
  15. {   
  16. this.x = 0;   
  17. this.xDirection = 1;   
  18. }else if (this.y >= canvas.height - 32)   
  19. {   
  20. this.y = canvas.height - 32;   
  21. this.yDirection = -1;   
  22. }else if (this.y <= 0)   
  23. {   
  24. this.y = 0;   
  25. this.yDirection = 1;   
  26. }   
  27. };   

怪物比英雄的定义要复杂得多。首先,怪物每隔五秒会增加一个(为增加难度),故不能单纯创建一个数组,而是需要一个类,再用类创建怪物对象。js当中只有类的半实现,具体使用function来创建。然后,怪物需要有撞墙返回的性质。

怪物的速度比英雄略慢,为100像素/秒。默认坐标在画布当中随机。怪物以45度移动。xDirection,yDirection合起来表示左上、左下、右上、右下4个方向。然后monster这个类有个move的动作,modifier表示两次刷新的时间间隔,可以计算出经过时间间隔后怪物的坐标。下面4个if函数用来判断,是否超越边界,超越则马上转向,以实现碰撞的效果。

  1. var monsterSum = 0;   
  2. var monsterList = new Array();   
  3. monsterList[monsterSum] = new monster(); 

前面用var已经创建了英雄的实例,但是monster我们只建立了类而已,接下来要实例化。monsterSum表示怪物的数量,为方便,按照c的习惯,从0开始技术,即0表示有一个怪物。monsterList用来表示一个存怪物对象的数组,然后顺带新建一个怪物。

Step 3 游戏动起来!

先添加一个事件来接收键盘的动作,上下左右用对应的ascii码。因为游戏并不是摁一下方向键,就移动一段距离。而是,判断一个时间间隔内的动作(最后的动作,中间有可能会变化,故用数组保存最后结果)。

  1. var keysDown = {};   
  2. addEventListener("keydown", function (e) {   
  3. keysDown[e.keyCode] = true;//如果有"keydown"这个动作,即摁下某键,就会存进keysDown数组   
  4. }, false);   
  5. addEventListener("keyup", function (e) {   
  6. delete keysDown[e.keyCode];   
  7. }); 

下面上主函数

  1. var main = function () {   
  2. var now = Date.now();   
  3. var delta = now - then;   
  4. Move(delta / 1000);//每次间隔时间根本不是1ms,比1ms要大得多   
  5. Draw();   
  6. Check();   
  7. then = now;   

main函数就是主函数,就是一个刷新所执行的函数。now、then两个变量记录两次刷新的时间间隔,这个时间间隔并不是固定的,一般为几百毫秒。delta 是两者之差,单位为毫秒。下面依次解释各个函数:Move()用来计算英雄和怪物的新位置。Draw()用来画背景、人物、文字。Check()用来检查,怪物和英雄是否相撞。

Move():

  1. var Move = function (modifier) {   
  2. if (38 in keysDown) {   
  3. hero.y -hero.speed * modifier;   
  4. }   
  5. if (40 in keysDown) {   
  6. hero.y += hero.speed * modifier;   
  7. }   
  8. if (37 in keysDown) {   
  9. hero.x -hero.speed * modifier;   
  10. }   
  11. if (39 in keysDown) {   
  12. hero.x += hero.speed * modifier;   
  13. }   
  14. if (hero.x >= canvas.width - 32) {   
  15. hero.x = 0;   
  16. }else if (hero.x <= 0) {   
  17. hero.x = canvas.width - 32;   
  18. }   
  19. if (hero.y >= canvas.height - 32) {   
  20. hero.y = 0;   
  21. }else if (hero.y <= 0) {   
  22. hero.y = canvas.height - 32;   
  23. }   
  24. for (var i = 0; i <= monsterSum; i++) {   
  25. monsterList[i].move(modifier);   
  26. }   

这里很好理解,判断这段时间间隔英雄的动作。算出新位置后,判断英雄是否跑出了画布,跑出了就从另一头出来(感谢 @昭曈 的创意)。接下来依次调用各个怪物的move函数,计算他们的新位置。

Draw():

  1. var Draw = function () {   
  2. if (bgReady) {   
  3. ctx.drawImage(bgImage, 0 ,0);   
  4. }   
  5. if (heroReady) {   
  6. ctx.drawImage(heroImage, hero.x, hero.y);   
  7. }   
  8. if (monsterReady) {   
  9. for (var i = 0; i <= monsterSum; i++)   
  10. ctx.drawImage(monsterImage, monsterList[i].x, monsterList[i].y);   
  11. }   
  12. ctx.fillStyle = "rgb(250, 250, 250)";   
  13. ctx.font = "24px Helvetica";   
  14. ctx.textAlign = "left";   
  15. ctx.textBaseline = "top";   
  16. last = Date.now() - start;   
  17. ctx.fillText(last/1000, 32, canvas.height - 64);   

前三个函数类似,就是如果准备好了图片就画东西上去(前面的三个ready函数派上了用场)。下面先定义了文字的style,然后计算出时间间隔last,然后画上去。

Check():

  1. var Check = function () {   
  2. if (monsterSum != Math.floor(last / 5000)){//如果时间经过5秒就增加一个怪兽实例   
  3. monsterSum ++;   
  4. monsterList[monsterSum] = new monster();   
  5. }   
  6. for (var i = 0; i <= monsterSum; i++) {   
  7. if (   
  8. (monsterList[i].x - 32) <= hero.x   
  9. && hero.x <= (monsterList[i].x + 32)   
  10. && (monsterList[i].y - 32) <= hero.y   
  11. && hero.y <= (monsterList[i].y + 32)   
  12. ) {   
  13. end = Date.now();   
  14. alert("你坚持了" + (end - start)/1000 + "秒");   
  15. End();   
  16. }   
  17. }   

第一步是 如果经过5秒就增加一个怪兽,然后下面一个个判断怪兽与英雄是否接触。(这里用的是矩形,@小樟 说可以用圆心,感兴趣的可以试试: ) )

大家注意到了下面用到了一个end函数。下面补充,如果不停止一直刷新就浏览器就一直动了,故要有个结束函数。

End():

  1. var End = function () {   
  2. if (bgReady) {   
  3. ctx.drawImage(bgImage, 0 ,0); //留住背景   
  4. }   
  5. window.clearInterval(timer);   
  6. return;   

用到的clearInterval()稍后会说到。

Step 4 程序入口

  1. var then = Date.now();   
  2. var start = then;   
  3. timer = setInterval(main, 1); 

定义了初始的then和start,接下里用了setInterval(),意为每隔1ms就执行一次main函数。1ms是虚指,就是立即执行的意思。要想停止就用前面的clearInterval()函数。

到这里为止一个完整的游戏就写好了~ : )

后来 @志谦 实践出了一个bug,就是窗口如果最小化后,会一直计时,但不会被撞。问题在于高级的浏览器(当然没在说IE : ) ),默认如果最小化后,会终止interval、timeout这类函数的执行,以节约资源。但我们计时的方法是结束时间减去开始时间,所以就造成这个刷分的bug。感谢 @小樟 提供了main函数递归,全局变量表示是否停止的办法。

哦,第一次就那么没了~

源码下载地址:html5game

原文链接:http://billyellow.com/?p=10

【编辑推荐】

  1. 10个绝对让你疯狂的HTML5和JS实验性展示
  2. HTML5开发实战之网易微博
  3. JS游戏开发(二)让目标人物移动
  4. 如果HTML5是一个妹子,她长得像?
  5. 50个HTML5强悍效果Demo集合
【责任编辑:张伟 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

超级网管员——网络设备

本书深入细致地介绍了用于构建网络的最重要的硬件设备——交换机、路由器、安全设备和无线设备,涵盖了原理、参数、分类、适用、规划、接口...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊
× CTO训练营(深圳站)