如何利用HTML5 Canvas制作一个简单的打飞机游戏

智者创造机会,强者把握机会,弱者坐等机会。才能是来自独创性。独创性是思维观察明白和决定的一种独特的方式。

之前在当耐特的DEMO里看到个打飞机的游戏,然后就把他的图片和音频扒了了下来。。。。自己凭着玩的心情重新写了一个。仅供娱乐哈。。。。。。我没有用框架,所有js都是自己写的。。。。。。所以就可以来当个简单的好代码教程,对那些刚玩canvas的,或许能有些帮助,楼主玩canvas也不是很久,技术不是很好,请见谅哈。

  闲话不多说,先上DEMO撒:飞机游戏 楼主写这个人纯碎娱乐,没想着写成多正式的游戏哈。

  步入主题啦:打飞机游戏文件有index.html入口文件,allSprite.js精灵的逻辑处理文件,loading.js加载处理文件以及data.js(初始化的一些数据)。

  首先,正常的游戏基本上都需要一个loading,loading页面就是用来预加载数据的,包括精灵表图片,音频等,因为这是个小游戏,要加载的就只有一些音频和图片。里面的加载代码主要就下面这些,其他是制作loading动画的,那个比较简单,就不贴了,如果有兴趣的直接在DEMO里看控制台就行了:

XML/HTML Code复制内容到剪贴板
  1. loadImg:function(datas){
  2. var_this=this;
  3. vardataIndex=0;
  4. li();
  5. functionli(){
  6. if(datas[dataIndex].indexOf("mp3")>=0){
  7. varaudio=document.createElement("audio");
  8. document.body.appendChild(audio);
  9. audio.preload="auto";
  10. audio.src=datas[dataIndex];
  11. audio.oncanplaythrough=function(){
  12. this.oncanplaythrough=null;
  13. dataIndex++;
  14. if(dataIndex===datas.length){
  15. _this.percent=100;
  16. }else{
  17. _this.percent=parseInt(dataIndex/datas.length*100);
  18. li.call(_this);
  19. }
  20. }
  21. }else{
  22. preLoadImg(datas[dataIndex],function(){
  23. dataIndex++;
  24. if(dataIndex===datas.length){
  25. _this.percent=100;
  26. }else{
  27. _this.percent=parseInt(dataIndex/datas.length*100);
  28. li.call(_this);
  29. }
  30. })
  31. }
  32. }
  33. },
  34. //再贴出preLoadImg的方法
  35. functionpreLoadImg(src,callback){
  36. varimg=newImage();
  37. img.src=src;
  38. if(img.complete){
  39. callback.call(img);
  40. }else{
  41. img.onload=function(){
  42. callback.call(img);
  43. }
  44. }
  45. }


我先在data.js里面用一个数组保存文件的链接,然后判断这些链接是图片还是音频,如果是图片就用preLoadImg加载,预加载图片的代码很简单,就是new一个图片对象,然后把链接赋给它,加载完后再回调。音频的加载则是通过生成一个HTML5的audio dom对象,把链接赋给它,audio有一个事件“canplaythrough”,浏览器预计能够在不停下来进行缓冲的情况下持续播放指定的音频/视频时,会发生 canplaythrough 事件,也就是说当canplaythrough被调用时,音频就已经被加载的差不多了,可以进行下一个音频的加载了。就这样当把所有东西都加载完后,再进行回调,开始游戏。

  游戏开始了,一个游戏,会需要很多的对象,所以我就统一写成了一个精灵对象,不同对象之间的每一帧的运动情况直接用behavior来分别编写就行了。

XML/HTML Code复制内容到剪贴板
  1. W.Sprite=function(name,painter,behaviors,args){
  2. if(name!==undefined)this.name=name;
  3. if(painter!==undefined)this.painter=painter;
  4. this.top=0;
  5. this.left=0;
  6. this.width=0;
  7. this.height=0;
  8. this.velocityX=3;
  9. this.velocityY=2;
  10. this.visible=true;
  11. this.animating=false;
  12. this.behaviors=behaviors;
  13. this.rotateAngle=0;
  14. this.blood=50;
  15. this.fullBlood=50;
  16. if(name==="plan"){
  17. this.rotateSpeed=0.05;
  18. this.rotateLeft=false;
  19. this.rotateRight=false;
  20. this.fire=false;
  21. this.firePerFrame=10;
  22. this.fireLevel=1;
  23. }elseif(name==="star"){
  24. this.width=Math.random()*2;
  25. this.speed=1*this.width/2;
  26. this.lightLength=5;
  27. this.cacheCanvas=document.createElement("canvas");
  28. thisthis.cacheCtx=this.cacheCanvas.getContext('2d');
  29. thisthis.cacheCanvas.width=this.width+this.lightLength*2;
  30. thisthis.cacheCanvas.height=this.width+this.lightLength*2;
  31. this.painter.cache(this);
  32. }elseif(name==="badPlan"){
  33. this.badKind=1;
  34. this.speed=2;
  35. this.rotateAngle=Math.PI;
  36. }elseif(name==="missle"){
  37. this.width=missleWidth;
  38. }elseif(name==="boom"){
  39. this.width=boomWidth;
  40. }elseif(name==="food"){
  41. this.width=40;
  42. this.speed=3;
  43. this.kind="LevelUP"
  44. }
  45. this.toLeft=false;
  46. this.toTop=false;
  47. this.toRight=false;
  48. this.toBottom=false;
  49. this.outArcRadius=Math.sqrt((this.width/2*this.width/2)*2);
  50. if(args){
  51. for(vararginargs){
  52. this[arg]=args[arg];
  53. }
  54. }
  55. }
  56. Sprite.prototype={
  57. constructor:Sprite,
  58. paint:function(){
  59. if(this.name==="badPlan"){this.update();}
  60. if(this.painter!==undefined&&this.visible){
  61. if(this.name!=="badPlan"){
  62. this.update();
  63. }
  64. if(this.name==="plan"||this.name==="missle"||this.name==="badPlan"){
  65. ctx.save();
  66. ctx.translate(this.left,this.top);
  67. ctx.rotate(this.rotateAngle);
  68. this.painter.paint(this);
  69. ctx.restore();
  70. }else{
  71. this.painter.paint(this);
  72. }
  73. }
  74. },
  75. update:function(time){
  76. if(this.behaviors){
  77. for(vari=0;i<this.behaviors.length;i++){
  78. this.behaviors[i].execute(this,time);
  79. }
  80. }
  81. }
  82. }


写出精灵类后,就可以通过编写每个的painter以及behavior来生成不同的对象了。接下来就是写painter了,painter分成两种,一种是普通的painter,一种就是精灵表painter,因为像爆炸动画,飞机开枪动画,都不是一张图片就能搞定的,所以就需要用到精灵表了:

而绘制这些就要为他们定制一个精灵表绘制器,下面这个是最简单的精灵表绘制器,针对游戏的复杂性可以相对的修改精灵表写法,直到合适,不过原理都大同小异,就是小修小改而已:

XML/HTML Code复制内容到剪贴板
  1. varSpriteSheetPainter=function(cells){
  2. this.cells=cells||[];
  3. this.cellIndex=0;
  4. }
  5. SpriteSheetPainter.prototype={
  6. advance:function(){
  7. if(this.cellIndex===this.cells.length-1){
  8. this.cellIndex=0;
  9. }
  10. elsethis.cellIndex++;
  11. },
  12. paint:function(sprite){
  13. varcell=this.cells[this.cellIndex];
  14. context.drawImage(spritesheet,cell.x,cell.y,cell.w,cell.h,sprite.left,sprite.top,cell.w,cell.h);
  15. }
  16. }

而普通的绘制器就更简单了,直接写一个painter,把要画的什么东西都写进去就行了。

有了精灵类和精灵表绘制器后,我们就可以把星星,飞机,子弹,爆炸对象都写出来了:下面是整个allSprite.js的代码:

JavaScript Code复制内容到剪贴板
  1. (function(W){
  2. "usestrict"
  3. varplanWidth=24,
  4. planHeight=24,
  5. missleWidth=70,
  6. missleHeight=70,
  7. boomWidth=60;
  8. //精灵类
  9. W.Sprite=function(name,painter,behaviors,args){
  10. if(name!==undefined)this.name=name;
  11. if(painter!==undefined)this.painter=painter;
  12. this.top=0;
  13. this.left=0;
  14. this.width=0;
  15. this.height=0;
  16. this.velocityX=3;
  17. this.velocityY=2;
  18. this.visible=true;
  19. this.animating=false;
  20. this.behaviors=behaviors;
  21. this.rotateAngle=0;
  22. this.blood=50;
  23. this.fullBlood=50;
  24. if(name==="plan"){
  25. this.rotateSpeed=0.05;
  26. this.rotateLeft=false;
  27. this.rotateRight=false;
  28. this.fire=false;
  29. this.firePerFrame=10;
  30. this.fireLevel=1;
  31. }elseif(name==="star"){
  32. this.width=Math.random()*2;
  33. this.speed=1*this.width/2;
  34. this.lightLength=5;
  35. this.cacheCanvas=document.createElement("canvas");
  36. this.cacheCtx=this.cacheCanvas.getContext('2d');
  37. this.cacheCanvas.width=this.width+this.lightLength*2;
  38. this.cacheCanvas.height=this.width+this.lightLength*2;
  39. this.painter.cache(this);
  40. }elseif(name==="badPlan"){
  41. this.badKind=1;
  42. this.speed=2;
  43. this.rotateAngle=Math.PI;
  44. }elseif(name==="missle"){
  45. this.width=missleWidth;
  46. }elseif(name==="boom"){
  47. this.width=boomWidth;
  48. }elseif(name==="food"){
  49. this.width=40;
  50. this.speed=3;
  51. this.kind="LevelUP"
  52. }
  53. this.toLeft=false;
  54. this.toTop=false;
  55. this.toRight=false;
  56. this.toBottom=false;
  57. this.outArcRadius=Math.sqrt((this.width/2*this.width/2)*2);
  58. if(args){
  59. for(vararginargs){
  60. this[arg]=args[arg];
  61. }
  62. }
  63. }
  64. Sprite.prototype={
  65. constructor:Sprite,
  66. paint:function(){
  67. if(this.name==="badPlan"){this.update();}
  68. if(this.painter!==undefined&&this.visible){
  69. if(this.name!=="badPlan"){
  70. this.update();
  71. }
  72. if(this.name==="plan"||this.name==="missle"||this.name==="badPlan"){
  73. ctx.save();
  74. ctx.translate(this.left,this.top);
  75. ctx.rotate(this.rotateAngle);
  76. this.painter.paint(this);
  77. ctx.restore();
  78. }else{
  79. this.painter.paint(this);
  80. }
  81. }
  82. },
  83. update:function(time){
  84. if(this.behaviors){
  85. for(vari=0;i<this.behaviors.length;i++){
  86. this.behaviors[i].execute(this,time);
  87. }
  88. }
  89. }
  90. }
  91. //精灵表绘制器
  92. W.SpriteSheetPainter=function(cells,isloop,endCallback,spritesheet){
  93. this.cells=cells||[];
  94. this.cellIndex=0;
  95. this.dateCount=null;
  96. this.isloop=isloop;
  97. this.endCallback=endCallback;
  98. this.spritesheet=spritesheet;
  99. }
  100. SpriteSheetPainter.prototype={
  101. advance:function(){
  102. this.cellIndex=this.isloop?(this.cellIndex===this.cells.length-1?0:this.cellIndex+1):(this.cellIndex+1);
  103. },
  104. paint:function(sprite){
  105. if(this.dateCount===null){
  106. this.dateCount=newDate();
  107. }else{
  108. varnewd=newDate();
  109. vartc=newd-this.dateCount;
  110. if(tc>40){
  111. this.advance();
  112. this.dateCount=newd;
  113. }
  114. }
  115. if(this.cellIndex<this.cells.length||this.isloop){
  116. varcell=this.cells[this.cellIndex];
  117. ctx.drawImage(this.spritesheet,cell.x,cell.y,cell.w,cell.h,sprite.left-sprite.width/2,sprite.top-sprite.width/2,cell.w,cell.h);
  118. }elseif(this.endCallback){
  119. this.endCallback.call(sprite);
  120. this.cellIndex=0;
  121. }
  122. }
  123. }
  124. //特制飞机精灵表绘制器
  125. W.controllSpriteSheetPainter=function(cells,spritesheet){
  126. this.cells=cells||[];
  127. this.cellIndex=0;
  128. this.dateCount=null;
  129. this.isActive=false;
  130. this.derection=true;
  131. this.spritesheet=spritesheet;
  132. }
  133. controllSpriteSheetPainter.prototype={
  134. advance:function(){
  135. if(this.isActive){
  136. this.cellIndex++;
  137. if(this.cellIndex===this.cells.length){
  138. this.cellIndex=0;
  139. this.isActive=false;
  140. }
  141. }
  142. },
  143. paint:function(sprite){
  144. if(this.dateCount===null){
  145. this.dateCount=newDate();
  146. }else{
  147. varnewd=newDate();
  148. vartc=newd-this.dateCount;
  149. if(tc>sprite.firePerFrame){
  150. this.advance();
  151. this.dateCount=newd;
  152. }
  153. }
  154. varcell=this.cells[this.cellIndex];
  155. ctx.drawImage(this.spritesheet,cell.x,cell.y,cell.w,cell.h,-planWidth/2,-planHeight/2,cell.w,cell.h);
  156. }
  157. }
  158. W.planBehavior=[
  159. {execute:function(sprite,time){
  160. if(sprite.toTop){
  161. sprite.top=sprite.top<planHeight/2?sprite.top:sprite.top-sprite.velocityY;
  162. }
  163. if(sprite.toLeft){
  164. sprite.left=sprite.left<planWidth/2?sprite.left:sprite.left-sprite.velocityX;
  165. }
  166. if(sprite.toRight){
  167. sprite.left=sprite.left>canvas.width-planWidth/2?sprite.left:sprite.left+sprite.velocityX;
  168. }
  169. if(sprite.toBottom){
  170. sprite.top=sprite.top>canvas.height-planHeight/2?sprite.top:sprite.top+sprite.velocityY;
  171. }
  172. if(sprite.rotateLeft){
  173. sprite.rotateAngle-=sprite.rotateSpeed;
  174. }
  175. if(sprite.rotateRight){
  176. sprite.rotateAngle+=sprite.rotateSpeed;
  177. }
  178. if(sprite.fire&&!sprite.painter.isActive){
  179. sprite.painter.isActive=true;
  180. this.shot(sprite);
  181. }
  182. },
  183. shot:function(sprite){
  184. this.addMissle(sprite,sprite.rotateAngle);
  185. varmissleAngle=0.1
  186. for(vari=1;i<sprite.fireLevel;i++){
  187. this.addMissle(sprite,sprite.rotateAngle-i*missleAngle);
  188. this.addMissle(sprite,sprite.rotateAngle+i*missleAngle);
  189. }
  190. varaudio=document.getElementsByTagName("audio");
  191. for(vari=0;i<audio.length;i++){
  192. console.log(audio[i].paused)
  193. if(audio[i].src.indexOf("shot")>=0&&audio[i].paused){
  194. audio[i].play();
  195. break;
  196. }
  197. }
  198. },
  199. addMissle:function(sprite,angle){
  200. for(varj=0;j<missles.length;j++){
  201. if(!missles[j].visible){
  202. missles[j].left=sprite.left;
  203. missles[j].top=sprite.top;
  204. missles[j].rotateAngle=angle;
  205. varmissleSpeed=20;
  206. missles[j].velocityX=missleSpeed*Math.sin(-missles[j].rotateAngle);
  207. missles[j].velocityY=missleSpeed*Math.cos(-missles[j].rotateAngle);
  208. missles[j].visible=true;
  209. break;
  210. }
  211. }
  212. }
  213. }
  214. ]
  215. W.starBehavior=[
  216. {execute:function(sprite,time){
  217. if(sprite.top>canvas.height){
  218. sprite.left=Math.random()*canvas.width;
  219. sprite.top=Math.random()*canvas.height-canvas.height;
  220. }
  221. sprite.top+=sprite.speed;
  222. }}
  223. ]
  224. W.starPainter={
  225. paint:function(sprite){
  226. ctx.drawImage(sprite.cacheCanvas,sprite.left-sprite.width/2-sprite.lightLength,sprite.top-sprite.width/2-sprite.lightLength)
  227. },
  228. cache:function(sprite){
  229. sprite.cacheCtx.save();
  230. varopacity=0.5,addopa=1/sprite.lightLength;
  231. sprite.cacheCtx.fillStyle="rgba(255,255,255,0.8)";
  232. sprite.cacheCtx.beginPath();
  233. sprite.cacheCtx.arc(sprite.width/2+sprite.lightLength,sprite.width/2+sprite.lightLength,sprite.width/2,0,2*Math.PI);
  234. sprite.cacheCtx.fill();
  235. for(vari=1;i<=sprite.lightLength;i+=2){
  236. opacity-=addopa;
  237. sprite.cacheCtx.fillStyle="rgba(255,255,255,"+opacity+")";
  238. sprite.cacheCtx.beginPath();
  239. sprite.cacheCtx.arc(sprite.width/2+sprite.lightLength,sprite.width/2+sprite.lightLength,sprite.width/2+i,0,2*Math.PI);
  240. sprite.cacheCtx.fill();
  241. }
  242. }
  243. }
  244. W.foodBehavior=[
  245. {execute:function(sprite,time){
  246. sprite.top+=sprite.speed;
  247. if(sprite.top>canvas.height+sprite.width){
  248. sprite.visible=false;
  249. }
  250. }}
  251. ]
  252. W.foodPainter={
  253. paint:function(sprite){
  254. ctx.fillStyle="rgba("+parseInt(Math.random()*255)+","+parseInt(Math.random()*255)+","+parseInt(Math.random()*255)+",1)"
  255. ctx.font="15px微软雅黑"
  256. ctx.textAlign="center";
  257. ctx.textBaseline="middle";
  258. ctx.fillText(sprite.kind,sprite.left,sprite.top);
  259. }
  260. }
  261. W.missleBehavior=[{
  262. execute:function(sprite,time){
  263. sprite.left-=sprite.velocityX;
  264. sprite.top-=sprite.velocityY;
  265. if(sprite.left<-missleWidth/2||sprite.top<-missleHeight/2||sprite.left>canvas.width+missleWidth/2||sprite.top<-missleHeight/2){
  266. sprite.visible=false;
  267. }
  268. }
  269. }];
  270. W.misslePainter={
  271. paint:function(sprite){
  272. varimg=newImage();
  273. img.src="../planGame/image/plasma.png"
  274. ctx.drawImage(img,-missleWidth/2+1,-missleHeight/2+1,missleWidth,missleHeight);
  275. }
  276. }
  277. W.badPlanBehavior=[{
  278. execute:function(sprite,time){
  279. if(sprite.top>canvas.height||!sprite.visible){
  280. varrandom=Math.random();
  281. if(point>=200&&point<400){
  282. sprite.fullBlood=150;
  283. if(random<0.1){
  284. sprite.badKind=2;
  285. sprite.fullBlood=250;
  286. }
  287. }elseif(point>=400&&point<600){
  288. sprite.fullBlood=250;
  289. if(random<0.2){
  290. sprite.badKind=2;
  291. sprite.fullBlood=400;
  292. }
  293. if(random<0.1){
  294. sprite.badKind=3;
  295. sprite.fullBlood=600;
  296. }
  297. }elseif(point>=600){
  298. sprite.fullBlood=500;
  299. if(random<0.4){
  300. sprite.badKind=2;
  301. sprite.fullBlood=700;
  302. }
  303. if(random<0.2){
  304. sprite.badKind=3;
  305. sprite.fullBlood=1000;
  306. }
  307. }
  308. sprite.visible=true;
  309. sprite.blood=sprite.fullBlood;
  310. sprite.left=Math.random()*(canvas.width-2*planWidth)+planWidth;
  311. sprite.top=Math.random()*canvas.height-canvas.height;
  312. }
  313. sprite.top+=sprite.speed;
  314. },
  315. shot:function(sprite){
  316. this.addMissle(sprite,sprite.rotateAngle);
  317. varmissleAngle=0.1
  318. for(vari=1;i<sprite.fireLevel;i++){
  319. this.addMissle(sprite,sprite.rotateAngle-i*missleAngle);
  320. this.addMissle(sprite,sprite.rotateAngle+i*missleAngle);
  321. }
  322. },
  323. addMissle:function(sprite,angle){
  324. for(varj=0;j<missles.length;j++){
  325. if(!missles[j].visible){
  326. missles[j].left=sprite.left;
  327. missles[j].top=sprite.top;
  328. missles[j].rotateAngle=angle;
  329. varmissleSpeed=20;
  330. missles[j].velocityX=missleSpeed*Math.sin(-missles[j].rotateAngle);
  331. missles[j].velocityY=missleSpeed*Math.cos(-missles[j].rotateAngle);
  332. missles[j].visible=true;
  333. break;
  334. }
  335. }
  336. }
  337. }];
  338. W.badPlanPainter={
  339. paint:function(sprite){
  340. varimg=newImage();
  341. img.src="../planGame/image/ship.png"
  342. switch(sprite.badKind){
  343. case1:ctx.drawImage(img,96,0,planWidth,planWidth,-planWidth/2,-planHeight/2,planWidth,planWidth);
  344. break;
  345. case2:ctx.drawImage(img,120,0,planWidth,planWidth,-planWidth/2,-planHeight/2,planWidth,planWidth);
  346. break;
  347. case3:ctx.drawImage(img,144,0,planWidth,planWidth,-planWidth/2,-planHeight/2,planWidth,planWidth);
  348. break;
  349. }
  350. ctx.strokeStyle="#FFF";
  351. ctx.fillStyle="#F00";
  352. varbloodHeight=1;
  353. ctx.strokeRect(-planWidth/2-1,planHeight+bloodHeight+3,planWidth+2,bloodHeight+2);
  354. ctx.fillRect(planWidth/2-planWidth*sprite.blood/sprite.fullBlood,planHeight+bloodHeight+3,planWidth*sprite.blood/sprite.fullBlood,bloodHeight);
  355. }
  356. }
  357. W.planSize=function(){
  358. return{
  359. w:planWidth,
  360. h:planHeight
  361. }
  362. }
  363. })(window);

这些绘制方法之类的都相对比较简单。

  主要说一下飞机的运动以及对象数量的控制,飞机怎么运动?毫无疑问,通过键盘控制它运动,可能很多人就会想到通过keydown这个方法按下的时候通过判断keyCode来让飞机持续运动。但是有个问题,keydown事件不支持多键按下,也就是说,当你按下X键时,keyCode是88,与此同时你按下方向键后,keyCode会瞬间变成37,也就是说,如果你单纯的想靠keydown来控制飞机运动,飞机就只能做一件事,要么只可以往某个方向移动,要么只会开枪。

  所以,我们要通过keydown和keyup来实现飞机的运动,原理很容易理解:当我们按下往左的方向键时,我们给飞机一个往左的状态,也就是让飞机的toLeft属性为true,而在动画循环中,判断飞机的状态,如果toLeft为true则飞机的x值不停地减少,飞机也就会不停地往左移动,然后当我们抬起手指时触发keyup事件,我们就再keyup事件中解除飞机往左的状态。飞机也就停止往左移动了。其他状态也一样的原理,这样写的话,就能够让飞机多种状态于一生了。可以同时开枪同时到处跑了。

实现的代码如下:

XML/HTML Code复制内容到剪贴板
  1. //keydown/keyup事件的绑定
  2. window.onkeydown=function(event){
  3. switch(event.keyCode){
  4. case88:myplan.fire=true;
  5. break;
  6. case90:myplan.rotateLeft=true;
  7. break;
  8. case67:myplan.rotateRight=true;
  9. break;
  10. case37:myplan.toLeft=true;
  11. break;
  12. case38:myplan.toTop=true;
  13. break;
  14. case39:myplan.toRight=true;
  15. break;
  16. case40:myplan.toBottom=true;
  17. break;
  18. }
  19. }
  20. window.onkeyup=function(event){
  21. switch(event.keyCode){
  22. case88:myplan.fire=false;
  23. break;
  24. case90:myplan.rotateLeft=false;
  25. break;
  26. case67:myplan.rotateRight=false;
  27. break;
  28. case37:myplan.toLeft=false;
  29. break;
  30. case38:myplan.toTop=false;
  31. break;
  32. case39:myplan.toRight=false;
  33. break;
  34. case40:myplan.toBottom=false;
  35. break;
  36. }
  37. }
  38. //飞机每一帧的状态更新处理代码
  39. execute:function(sprite,time){
  40. if(sprite.toTop){
  41. spritesprite.top=sprite.top<planHeight/2?sprite.top:sprite.top-sprite.velocityY;
  42. }
  43. if(sprite.toLeft){
  44. spritesprite.left=sprite.left<planWidth/2?sprite.left:sprite.left-sprite.velocityX;
  45. }
  46. if(sprite.toRight){
  47. spritesprite.left=sprite.left>canvas.width-planWidth/2?sprite.left:sprite.left+sprite.velocityX;
  48. }
  49. if(sprite.toBottom){
  50. spritesprite.top=sprite.top>canvas.height-planHeight/2?sprite.top:sprite.top+sprite.velocityY;
  51. }
  52. if(sprite.rotateLeft){
  53. sprite.rotateAngle-=sprite.rotateSpeed;
  54. }
  55. if(sprite.rotateRight){
  56. sprite.rotateAngle+=sprite.rotateSpeed;
  57. }
  58. if(sprite.fire&&!sprite.painter.isActive){
  59. sprite.painter.isActive=true;
  60. this.shot(sprite);
  61. }

就是如此简单。

  然后说下对象控制,打飞机游戏,会发射大量子弹,产生大量对象,包括爆炸啊,飞机啊,子弹等,如果不停地进行对象的生成和销毁,会让浏览器的负荷变得很大,运行了一段时间后就会卡出翔了。所以,我们要用可以循环利用的对象来解决这个问题,不进行对象的销毁,对所有对象进行保存,循环利用。

  我的做法就是,在游戏初始化的时候,直接生成一定数量的对象,存放在数组里面。当我们需要一个对象的时候,就从里面取,当用完后,再放回数组里面。数组里的所有对象都有一个属性,visible,代表对象当前是否可用。

  举个例子,当我的飞机发射一发炮弹,我需要一发炮弹,所以我就到炮弹数组里遍历,如果遍历到的炮弹visible为true,也就说明该对象正在使用着,不能拿来用,所以继续遍历,直到遍历到visible为false的炮弹对象,说明这个对象暂时没人用。然后就可以拿过来重新设置属性,投入使用了。当炮弹击中敌人或者打出画布外的时候,把炮弹的visible设成false,又成了一个没人用的炮弹在数组里存放起来等待下一次调用。

  所以,我们要预算算好页面大概要用到多少个对象,然后就预先准备好对象,这样,在游戏进行中,不会有对象进行生成和销毁,对游戏性能方面就有了提升了。

  

  最后再说下音频,游戏里面要用到多个同样的audio才能保证音效的不间断性:
复制代码

XML/HTML Code复制内容到剪贴板
  1. varaudio=document.getElementsByTagName("audio");
  2. for(vari=0;i<audio.length;i++){
  3. console.log(audio[i].paused)
  4. if(audio[i].src.indexOf("boom")>=0&&audio[i].paused){
  5. audio[i].play();
  6. break;
  7. }
  8. }

好吧,基本上就这样了。技术或许还不够好,纯碎做个记录,如果代码有不当正处,欢迎指出,共同学习。

源码地址:https://github.com/whxaxes/canvas-test/tree/gh-pages/src/Game-demo/planGame

本文如何利用HTML5 Canvas制作一个简单的打飞机游戏到此结束。盛年不重来,一日难再晨,及时当勉励,岁月不待人。小编再次感谢大家对我们的支持!

您可能有感兴趣的文章
html5如何实现微信打飞机游戏

如何使用canvas对video视频某一刻截图功能

Canvas绘制像素风图片的示例代码

canvas画图被放大且模糊的如何解决方法

Canvas获取视频第一帧缩略图的如何实现