代码
DEMO

不管写的过程中觉得有多便秘,写完了回过头再去看这个游戏其实并不算多么的复杂,一些基本的问题处理好就行——这也是这篇文章所想要说明的东西。因此这篇博客只能算是记录了一下写一个游戏过程中的一些思路,如果有同学也想要自己写一个游戏并不知道如何开始的话,我推荐下面两个内容:

如何开发一个简单的HTML5 Canvas 小游戏

HTML5小游戏—爱心鱼


键盘事件触发问题:

如果需要玩家通过按键操控坦克进行运动,很多人第一个想到的应该就是把相应的运动函数绑定到相应按键的onkeydown事件之上。

一般来说这么写有一个问题,那就是为了防止诸如像老人松手慢导至键盘事件多次触发这种情况,只有当你按下按键到一定的时间以后事件才会连续进行触发。

这个问题反应到游戏上就是你的坦克总是要在你按下按键后过一段时间才会开始连续运动,非常影响游戏体验。

这个问题的解决方法很简单:

let keyInfo = {};     //按键是否被按下的信息
let aKey = [72 , 74 , 87 , 83 , 65 , 68 , 38 , 40 , 37 , 39 , 17];      //这里面的数字是wasdhj等按键的键值for (let i = 0; i < aKey.length; i++) {keyInfo[aKey[i]] = {pressed : false}
}
  • 将按键的键值作为属性名,将按键状态储存到keyInfo对象中,初始值都为false,表明按键未按下。
  • 在按下键盘上相应的按键的时候,通过事件委托直接捕获到按下按键的keyCode也就是键值。
  • onkeydown事件触发以后将keyInfo中对应的属性设置为true,表明按键被按下,在onkeyup事件触发以后再将keyInfo中对应的属性设置为false。
  • 最后在游戏中循环检测keyInfo中对应按键的属性的真假并执行相应的操作就可以了

路径问题:

在不提坦克与子弹之间的碰撞问题的前提下,路径问题基本上就是在确定你的坦克跟子弹(子弹的问题其实更复杂一点,后面再详细讨论)在地图上哪里能走哪里不能走,虽然这个问题并不是很复杂,但在我看来这个问题可以说是整个游戏的核心所在,因为后面很多问题都是围绕着路劲而来。

要搞清楚路劲问题还是先要有一些准备工作:

1、地图:

游戏的主界面大小为416*416像素,总共由13*13个32*32像素的区域构成。

游戏中的坦克,障碍物及奖励的图片的大小都是32*32像素,因此只要使用一个13*13的数组就能将整个地图数据给存储下来了。

障碍物图片:

第一关地图数据:

let mapData[0] =[   //0代表空白,1代表32*32的砖块,2代表32*16的砖块,后面类推[0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0],[0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0],[0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0],[0 , 1 , 0 , 1 , 0 , 1 , 6 , 1 , 0 , 1 , 0 , 1 , 0],[0 , 1 , 0 , 1 , 0 , 2 , 0 , 2 , 0 , 1 , 0 , 1 , 0],[0 , 2 , 0 , 2 , 0 , 4 , 0 , 4 , 0 , 2 , 0 , 2 , 0],[4 , 0 , 4 , 4 , 0 , 2 , 0 , 2 , 0 , 4 , 4 , 0 , 4],[7 , 0 , 2 , 2 , 0 , 4 , 0 , 4 , 0 , 2 , 2 , 0 , 7],[0 , 4 , 0 , 4 , 0 , 1 , 1 , 1 , 0 , 4 , 0 , 4 , 0],[0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0],[0 , 1 , 0 , 1 , 0 , 2 , 0 , 2 , 0 , 1 , 0 , 1 , 0],[0 , 1 , 0 , 1 , 0 , 18 , 4 , 17 , 0 , 1 , 0 , 1 , 0],[0 , 0 , 0 , 0 , 0 , 3 , 15 , 5 , 0 , 0 , 0 , 0 , 0]
];

有了地图数据以后,只要循环调用drawImage方法就能够将地图画出来了:

for (let i = 0; i < 13; i++) {for(let j = 0; j < 13; j++){//获取对应的值let iData = mapData[0][i][j];if (iData) {//如果获取的值不为0,那么开始绘制地图cxt.drawImage(oImg, 32 * iData, 0, 32, 32, 32*j, 32*i, 32, 32);}}
}

第一关界面:

2、子弹的初始坐标

其他先不说,看这张图,最大的那个32px的方块表示的就是一个坦克,左上角顶点为其坐标,四个8px的小方块表示四个方向发射的子弹,坐标也是左上角。

很明显当坦克在发射子弹的时候,子弹的初始坐标必须要根据坦克的坐标及方向进行调整,不然发射的子弹位置就不对了。

//iDir表示坦克的方向,x和y的初始值也是坦克的坐标,这里需要根据坦克的方向进行调整才是子弹的初始坐标
// 1、3
if (iDir%2) {y += 12;x += 24*(+!(iDir-1));
// 0, 2
} else {x += 12;y += 24*iDir/2;
}

3、坦克转换方向后的对齐

大家可以看上面那个第一关的图片,很容易就能看出来没有砖块的黑色路径其实跟坦克的宽度是差不多的,当然,他们的宽度都是32个像素。

那么问题来了,游戏中玩家的坦克每次循环(一次循环16毫秒到17毫秒不等)会移动两个像素,除了一开始坦克正好对准了位置以外,以后每次转换方向,玩家根本没办法做到每次都是分毫不差的卡到那个32像素的点上,那么按照游戏的一般规定,对不起你前面有障碍物你无法通过。。。

因此,我们需要在坦克每次改变方向之后,都要正好对准这么一个点,代码如下:

// 在坦克转换方向后重新定位坦克的位置,使坦克当前移动方向的左边正好能够整除16,这样就正好对齐了砖块的契合处
//iDir表示坦克当前的方向02表上下,13表右左
//x表示坦克当前的横坐标,y表示坦克当前的纵坐标
if (iDir % 2) {  y = Math.round(y / 16);
} else {x = Math.round(x / 16) * 16;
}

如果仔细看了代码,可能有人心中就会出现一个疑问,为什么是要能够整除16?OK,下面就来回答这个问题。

4、路径数据

先回过头来看看上面那张障碍物的图片,拿灰褐色的砖块来说,很明显可以看到砖块一共有四种尺寸7张图,最小是16*16px,最大是32*32px。

想要告诉坦克或者子弹哪里有障碍物能否通过有两种方式:

  • 一是将每一种状态的砖块都保存下来,这样砖块跟钢筋加起来共十四种状态,判断起来过于麻烦,而且子弹打掉砖块后的判断也相应增加了变化的情况。

  • 二是将砖块都分解为16*16的小砖块,这样就不需要判断砖块的尺寸了,然后用一个26*26的数组就能够将整个地图的路径情况给记录下来。

令:其实这里还有一个方法那就是把障碍物全部分解为16*16的尺寸,这样地图数据直接就是路径数据了。

碰撞检测问题:

1、具体有哪些碰撞:

所谓的碰撞检测,按照上面这张游戏截图来说明的话主要分为两类:

  • 坦克的碰撞,这里面又包括了:

    • 坦克与奖励的碰撞
    • 坦克与坦克的碰撞
    • 坦克与障碍物的碰撞
  • 子弹的碰撞,这里面又包括了:

    • 子弹与子弹的碰撞
    • 子弹与坦克的碰撞
    • 子弹与障碍物的碰撞

2、坦克与奖励、子弹与子弹以及坦克与坦克的碰撞:

①、坦克与奖励以及子弹与子弹的碰撞的检测代码基本上没啥区别,因此只举坦克与奖励的碰撞来说明:

//坦克与奖励的碰撞检测
//坦克的x、y坐标分别减去奖励的x、y坐标,如果都小于一个坦克的大小32,那么表明坦克与奖励已经碰撞
let xVal = Math.abs(tank.x - bonus.x),yVal = Math.abs(tank.y - bonus.y);if (xVal < 32 && yVal < 32) {//碰撞了,执行相应的代码
}

如上面代码所示,他们之间的碰撞检测主要就是检查横纵坐标之差的绝对值,如果这两个值都小于坦克本身的尺寸,那么表明他们碰到了一起。

子弹同理,不过是检测是否小于子弹本身的尺寸8就可以了。

②、表面上看坦克与坦克的碰撞检测似乎与坦克与奖励、子弹与子弹的碰撞没什么不同,实际上还是有区别的,下面用一张图说明:

左边红色的NPC坦克正在渲染出生的动画,右边动画播放完成坦克开始运动,如果这里还是像之前那样去检测,很明显两个坦克已经碰到了一起,接下里两个坦克可能就都无法运动了。

那么坦克之间的碰撞检测如下:

//同样是检查x、y坐标的差值的绝对值
let xVal = Math.abs(this.x - tank.x),
let yVal = Math.abs(this.y - tank.y);
//这里根据方向的不同,检测的值也不同,这里的26留下的余地,如果两个坦克正好重叠,那么他们也是可以运动的
//iDir表示坦克当前的方向02表上下,13表右左
if (iDir % 2) {//iDir的值为1或者3,也就是坦克的方向是左右if (xVal < 32 && xVal > 26 && yVal < 32) {//...}
} else {//iDir的值为0或者2,也就是坦克的方向是上下if (yVal < 32 && yVal > 26 && xVal < 32) {//....}
}

这里判断的值之所以为26,拿y坐标来举例,如之前坦克转向后对齐里面的y = Math.round(y / 16)所示,在坦克转向后坦克坐标是会四舍五入的,因为移动速度最慢的坦克每个循环会移动1px,因此当(y / 16)< n.5的时候,n*16+1px ~ n* 16+7px会被舍弃,最多是6px,这样检测值是32-6=26正好能够让两个坦克在重叠后通过转向可以继续运动。

当然这里也会导至一个BUG,那就是某个时候如果我的坦克正好转向,坐标四舍五入后,有可能会导至两个坦克重叠,所以这里也需要在坦克转换方向后的做一个碰撞检测,如果正好有重叠那就不往那个方向转。

3、子弹与坦克的碰撞:

子弹与坦克的碰撞又是另外一回事了,之前也讲过子弹的坐标是根据发射子弹的坦克的坐标重新定位过的,因此检测的判断条件跟子弹的方向有很大的关系:

let x = bullet.x - oTank.x;
let y = bullet.y - oTank.y;
if (this.iDir % 2) {return (this.iDir -1)? (x < 32 && x > 0 && y > -8 && y < 32): (x > -8 && x < 0 && y > -8 && y < 32);
} else {return this.iDir? (y > -8 && y < 0 && x > -8 && x < 32): (y < 32 && y > 0 && x > -8 && x < 32);
}

用方向向上的子弹来举例:

上面两个32*32px的正方形表示坦克,下面那个8*8的正方形表示子弹,坦克与子弹的坐标都位于左上角的顶角处。

当坦克的x坐标位于横着的绿色线条中间之时(-8 <= bullet.x - tank.x <= 32),就表示子弹与坦克在横坐标上相碰撞了。

当坦克的y坐标位于竖着的绿线区域内时(0 <= bullet.y - tank.y<= 32),表示子弹与坦克在纵坐标上相碰撞了。

两个条件何在一起,就是方向向上的子弹与坦克的碰撞条件:

y < 32 && y > 0 && x > -8 && x < 32

4、坦克与障碍物的碰撞:

坦克与障碍物的碰撞实际上就是去判断最早的那个26*26的路径数组,看坦克当前方向上所对应的两个数组所代表的障碍物是否允许坦克通过。

代码并没有什么难度,唯一需要注意的是当坦克的方向是向上跟向左的时候,需要分别将传入的y与x坐标-1,这是因为你需要判断的是下一个路径数组的值,而不是当前。

5、子弹与障碍物的碰撞:

如果说子弹一次能打掉最少打掉的是16*16大小的障碍物的话,想要处理也非常简单,根据坐标将对应区域给cxt.clearReact掉,再将相应的路径数组置0就能够解决。

可惜问题并不是这么简单,为什么复杂呢?看下图就明白了:

这是坦克在没有吃掉星星的时候子弹所能打掉的砖块,通过对比我们能很清晰的看到子弹一次打掉的砖块是8*32的区域,等于说一个16*16的障碍物,我们得用两发子弹才能打掉,这就需要对于灰褐色的砖块进行特殊处理一下了:

let oBrickStatus = {}; //建立一个对象用来保存被子弹击中的砖块的状态
let iIndex = x/16*26 + y/16;  //因为路径数组的key值是使用x/16与y/16计算而来了,那么我们将这两个key值处理一下后得到一个新的数值,这个值用来作为记录被子弹击中的砖块的状态的key值
//如果oBrickStatus中没有保存这个砖块对应的记录,那么将[1, 1, 1, 1]赋值给oBrickStatus[iIndex]
//[1, 1, 1, 1]是因为一个16*16的区域正好可以分成4个8*8的区域,因此用这个数组记录下当前16*16的区域有哪些8*8的区域不存在(不存在的数组值为0)
if (!!oBrickStatus[iIndex]) {//这个函数用来计算子弹击中砖块后如何进行处理,下面单独进行介绍hitBrick();
} else{oBrickStatus[iIndex] = [1, 1, 1, 1];hitBrick();
}

经过上面那段代码,我们只需要去计算oBrickStatus[iIndex]的值,就将这个砖块的状态给保存了下来,如果以后子弹再打中了这个砖块,那么就拿出oBrickStatus[iIndex]的值来检查就可以了。

如图就表示了一个16*16的格子中四个数组项的分布情况,左边表示数组的索引,右边表示砖块是否被打掉(1表示存在,0表示否),一开始四个值都是1。

我们拿子弹方向向上来举例:

子弹向上的时候首先会检查索引值为2和3的数组项,当这两个值中间有一个不为0的时候,表明子弹与砖块碰撞了,那么使用clearReact清空掉相应的区域,并将索引值为2和3的数组项置0。

如果两个值都为0,那么子弹继续运动,再运动了8个像素后进入了数组项0和1表示的区域,此时再检查这两块区域所代表的是否为0,重复之前的操作。

最后在确定一个16*16的砖块全部被打掉后,直接将路径数组中的数据由表示砖块的1置为0,这样就实现了子弹对砖块的击中后的效果了。


以上就是我对于这个游戏的一些思考了,这些问题解决后整个游戏感觉就没什么需要注意的地方了,剩下的就是写了~~~

HTML5 坦克大战游戏的制作思路相关推荐

  1. 100行JS代码实现❤坦克大战js小游戏源码 HTML5坦克大战游戏代码(HTML+CSS+JavaScript )

    坦克大战js小游戏源码 HTML5坦克大战游戏代码(HTML+CSS+JavaScript ) HTML5坦克大战网页小游戏,完美还原小霸王学习机效果,以坦克战斗及保卫基地为主题,属于策略型类游戏. ...

  2. 基于HTML5坦克大战游戏简化版

    之前我们有分享过不少经典的HTML5游戏,有些还是很有意思的,比如HTML5版切水果游戏和HTML5中国象棋游戏.今天要分享的是一款简化版的HTML5坦克大战游戏,方向键控制坦克的行进方向,空格键发射 ...

  3. html5坦克大战游戏,HTML5游戏《坦克大战》完整源码

    [实例简介] HTML5游戏<坦克大战>完整源码 请不要商用,仅限学习使用 [实例截图] [核心代码] HTML5<坦克大战>完整源码 └── HTML5<坦克大战> ...

  4. html5坦克大战游戏,HTML5坦克大战游戏简化版

    本文作者html5tricks,转载请注明出处 之前我们有分享过不少经典的HTML5游戏,有些还是很有意思的,比如HTML5版切水果游戏和HTML5中国象棋游戏.今天要分享的是一款简化版的 JavaS ...

  5. HTML5制作坦克大战游戏+Canvas绘制基础图形——学习笔记一

    1.离线存储:1)存储到本地,可以离线浏览网页  2)不用cookie(安全性不太高,来回交互的数据量比较大) 2.语音识别 3.图像识别 4.HTML5游戏 5.CSS3的强大之处:动画和各种选择器 ...

  6. HTML5小游戏动手做(二):使用PIXI引擎制作坦克大战游戏

    这里写自定义目录标题 1. 简介 1.1 PIXI 简介 1.2 坦克大战游戏简介 2. PIXI 引擎入门 2.1 基本概念 2.1.1 舞台 Stage 2.1.2 容器 Container 2. ...

  7. 【java毕业设计】基于java+Socket+Eclipse的坦克大战游戏设计与实现(毕业论文+程序源码)——坦克大战游戏

    基于java+Socket+Eclipse的坦克大战游戏设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于java+Socket+Eclipse的坦克大战游戏设计与实现,文章末尾附有本毕业设 ...

  8. Java练手项目2:基于Java的坦克大战游戏(学习Java必备!!!)

    1.引言 随着社会和时代的进步,来自各个方面的压力让人没精打采,为了分解人们的压力,休养那变得疲顿的头脑和劳累的身心,特设计了坦克大战小游戏,游戏操作非常容易,只要将手指放在键盘上敲击相关的游戏键就可 ...

  9. python调用pygame_python使用pygame模块实现坦克大战游戏

    本文实例为大家分享了pygame模块实现坦克大战游戏的具体代码,供大家参考,具体内容如下 首先,第一步,游戏简单素材的准备. 炮弹, 炮弹,坦克移动.音乐-开火素材. 其次,思路整理. 我们需要几个类 ...

最新文章

  1. 关于深度学习的小知识点
  2. MySQL查询中LIMIT的大offset导致性能低下浅析
  3. 搜索引擎优化学习原理_如何使用数据科学原理来改善您的搜索引擎优化工作
  4. php是硬件还是软件,计算机操作系统是管理计算机硬件和软件的什么
  5. 我如何用 21 天在 GitHub 上获取 6300 个 Star?
  6. Chrome即将封杀Google Earth、Google Talk等插件
  7. 基于c语言智能空调控制系统设计,智能温室控制系统设计复习课程.docx
  8. 基于VUE,VUX组件开发的网易新闻页面搭建过程
  9. vue 跨域请求js添加代码
  10. 可以导出记录EXCEL表格的记账理财账本
  11. php处理头像,(头像处理)PHP把图片转换成圆形png
  12. 拒绝从网络访问这台计算机 注册表,拒绝访问的解决方法
  13. 菜鸟学IT之Hadoop综合大作业
  14. jxd android 4.1刷机包,金星JXD V3固件
  15. Spring MVC各组件近距离接触--下下--05
  16. 阿里云服务器部署可道云
  17. 英语练习30 Poor Dick
  18. 处理器的排名_waste waltz家用垃圾处理器排名图片
  19. SuperMap iClient for JavaScript 实现拖动半径进行距离查询
  20. Android Button 英文大小写问题

热门文章

  1. CentOS8使用gmssl搭建demoCA及配置OCSP服务
  2. JAVA拓展新的数据库_JavaEE 之 数据库技术 MySQL 拓展应用
  3. 音频信号发生器_ToneGenerator for Mac(音频发生器)
  4. Labview时间类型——以一个从计算机时钟获取日期和时间的综合运用为例
  5. 看架构 from:假如从餐饮店的角度来看架构…
  6. python关于矩阵的基本程序知识——使用Sympy模块
  7. 轻量级室内场景识别数据集:MIT-IndoorScene,百度云下载
  8. 关联矩阵与邻接矩阵的转换及Matlab实现
  9. CentOS7 下安装docker
  10. Guided Filter对三维点云降噪