首页 > 代码库 > H5 canvas 实现飞机大战游戏
H5 canvas 实现飞机大战游戏
首先看几张效果图:
上面三张图分别对应游戏的三种状态 ready,play,pause。体验一下
先介绍一下canvas 画图的原理,在这个游戏中的背景,飞机,子弹以及飞机被击中爆炸的效果都是一张张的图片,通过canvas的 drawImage() 函数把这一帧需要的所有图片按其所在的位置(坐标)画到画布上,当然有时候也需要画些文本,比如左上角的得分;然后接着画下一帧,同时改变飞机和子弹的位置;画下一帧之前一定要清除画布(通过这个函数 clearRect(x, y, width, height)),不然就是下图的效果啦:
辣眼睛!!!
不过在本例中因为每帧都要重新画上背景图,背景图又是填满整个画布的,所以画背景图时就等于把上一帧全部覆盖了,也就相当于清除画布了。
下面我们开始聊实现的细节:
加载需要的图片
在让游戏跑起来之前要先把需要的图片加载进来,类似:
代码如下:
1 // 所以图片的链接,包括背景图、各种飞机和飞机爆炸图、子弹图等 2 var imgName = [‘background.png‘, ‘game_pause_nor.png‘, ‘m1.png‘, ‘start.png‘, 3 // 敌机1 4 [‘enemy1.png‘, ‘enemy1_down1.png‘, ‘enemy1_down2.png‘, ‘enemy1_down3.png‘, ‘enemy1_down4.png‘], 5 // 敌机2 6 [‘enemy2.png‘, ‘enemy2_down1.png‘, ‘enemy2_down2.png‘, ‘enemy2_down3.png‘, ‘enemy2_down4.png‘], 7 // 敌机3 8 [‘enemy3_n1.png‘, ‘enemy3_n2.png‘, ‘enemy3_hit.png‘, ‘enemy3_down1.png‘, ‘enemy3_down2.png‘, ‘enemy3_down3.png‘, ‘enemy3_down4.png‘, ‘enemy3_down5.png‘, ‘enemy3_down6.png‘, ], 9 // 游戏loading图 10 [‘game_loading1.png‘, ‘game_loading2.png‘, ‘game_loading3.png‘, ‘game_loading4.png‘], 11 // 玩家飞机图 12 [‘hero1.png‘, ‘hero2.png‘, ‘hero_blowup_n1.png‘, ‘hero_blowup_n2.png‘, ‘hero_blowup_n3.png‘, ‘hero_blowup_n4.png‘] 13 ]; 14 // 存储不同类型的图片 15 var bg = null, 16 pause = null, 17 m = null, 18 startImg = null, 19 enemy1 = [], 20 enemy2 = [], 21 enemy3 = [], 22 gameLoad = [], 23 heroImg = []; 24 // 加载图片的进度 25 var progress = 1; 26 /*********加载图片*********/ 27 function download() { 28 bg = nImg(imgName[0]); 29 pause = nImg(imgName[1]); 30 m = nImg(imgName[2]); 31 startImg = nImg(imgName[3]); 32 for (var i = 0; i < imgName[4].length; i++) { 33 enemy1[i] = nImg(imgName[4][i]); 34 } 35 for (var i = 0; i < imgName[5].length; i++) { 36 enemy2[i] = nImg(imgName[5][i]); 37 } 38 for (var i = 0; i < imgName[6].length; i++) { 39 enemy3[i] = nImg(imgName[6][i]); 40 } 41 for (var i = 0; i < imgName[7].length; i++) { 42 gameLoad[i] = nImg(imgName[7][i]); 43 } 44 for (var i = 0; i < imgName[8].length; i++) { 45 heroImg[i] = nImg(imgName[8][i]); 46 } 47 48 function nImg(src) { 49 var img = new Image(); 50 img.src = http://www.mamicode.com/‘img/‘ + src; 51 img.onload = imgLoad; 52 return img; 53 } 54 // 绘制游戏加载进度画面 55 function imgLoad() { 56 progress += 3; 57 ctx.clearRect(0, 0, canvas.width, canvas.height); 58 var text = progress + ‘%‘; 59 var tw = ctx.measureText(text).width; 60 ctx.font = ‘60px arial‘; 61 ctx.fillStyle = ‘red‘; 62 ctx.lineWidth = ‘0‘; 63 ctx.strokeStyle = ‘#888‘; 64 //ctx.strokeText(text,(width-tw)/2,height/2); 65 ctx.fillText(text, (width - tw) / 2, height / 2); 66 if (progress >= 100) { 67 start(); 68 } 69 } 70 } 71 download();
其中有处理图片分类和加载进度的问题,代码有些冗余。
让背景动起来
从上面的游戏ready状态图可以看出游戏背景在不停的往上移动;
实现原理:连续画两张背景图到画布上,一上一下,第一张画在坐标为(0,0) 的位置,第二张紧接着第一张,然后每画一帧往上移动一点(一到两个像素吧),当上面的那张图片移出画布之后,将Y轴的坐标重置为0;代码如下:
1 var y = 0; 2 function paintBg() { 3 ctx.drawImage(bg, 0, y); // bg是背景图元素 4 ctx.drawImage(bg, 0, y - 852); 5 y++ == 852 && (y = 0); 6 }
构造玩家飞机(hero)
1 /*********构造hero************/ 2 var hero = null; 3 4 function Hero() { 5 this.x = (width - heroImg[0].width) / 2; // hero的坐标 6 this.y = height - heroImg[0].height; 7 this.index = 0; // 用于切换hero的图片 8 this.count = 0; // 用于控制hero图片切换的频率 9 this.hCount = 0; // 用于控制子弹发射的频率 10 this.eCount = 0; // 用于控制敌机出现的频率 11 this.n = 0; 12 this.draw = function() { 13 ctx.drawImage(heroImg[this.index], this.x, this.y); 14 ctx.fillText(‘SCORE:‘ + gameScore, 10, 30); 15 this.count++; 16 if (this.count % 3 == 0) { // 切换hero的图片 17 this.index = this.index == 0 ? 1 : 0; 18 this.count = 0; 19 } 20 this.hCount++; 21 if (this.hCount % 3 == 0) { // 同时生成三颗子弹 22 this.n == 32 && (this.n = 0); 23 hullet.push(new Hullet(this.n)); 24 this.n == 0 && (this.n = -32);; 25 hullet.push(new Hullet(this.n)); 26 this.n == -32 && (this.n = 32);; 27 hullet.push(new Hullet(this.n)); 28 this.hCount = 0; 29 } 30 this.eCount++; 31 if (this.eCount % 8 == 0) { //生成敌机 32 liveEnemy.push(new Enemy()); 33 this.eCount = 0; 34 } 35 } 36 37 function move(e) { 38 if (curPhase == PHASE_PLAY || curPhase == PHASE_PAUSE) { 39 curPhase = PHASE_PLAY; 40 var offsetX = e.offsetX || e.touches[0].pageX; 41 var offsetY = e.offsetY || e.touches[0].pageY; 42 var w = heroImg[0].width, 43 h = heroImg[0].height; 44 var nx = offsetX - w / 2, 45 ny = offsetY - h / 2; 46 nx < 20 - w / 2 ? nx = 20 - w / 2 : nx > (canvas.width - w / 2 - 20) ? nx = (canvas.width - w / 2 - 20) : 0; 47 ny < 0 ? ny = 0 : ny > (canvas.height - h / 2) ? ny = (canvas.height - h / 2) : 0; 48 hero.x = nx; 49 hero.y = ny; 50 hero.count = 2; 51 } 52 } 53 // 绑定鼠标移动和手指触摸事件,控制hero移动 54 canvas.addEventListener("mousemove", move, false); 55 canvas.addEventListener("touchmove", move, false); 56 // 鼠标移除时游戏暂停 57 canvas.onmouseout = function(e) { 58 if (curPhase == PHASE_PLAY) { 59 curPhase = PHASE_PAUSE; 60 } 61 } 62 }
本例中并没有设置hero的碰撞检测和生命值,所以英雄无敌!!!哈哈哈哈!!!
然并卵,我已经写不下去了;可是,坚持就是胜利呀;好吧,继续!
构造子弹
1 /**********构造子弹***********/ 2 var hullet = []; // 存储画布中所以子弹的数组 3 4 function Hullet(n) { 5 this.n = n; // 用于确定是左中右哪一颗子弹 6 // 子弹的坐标 7 this.mx = hero.x + (heroImg[0].width - m.width) / 2 + this.n; 8 this.my = this.n == 0 ? hero.y - m.height : hero.y + m.height; 9 this.width = m.width; // 子弹的宽和高 10 this.height = m.height; 11 this.removable = false; // 标识子弹是否可移除了 12 } 13 Hullet.drawHullet = function() { 14 for (var i = 0; i < hullet.length; i++) { //在画布上画出所以子弹 15 hullet[i].draw(); 16 if (hullet[i].removable) { // 如果为true就移除这颗子弹 17 hullet.splice(i, 1); 18 } 19 } 20 } 21 Hullet.prototype.draw = function() { // 在画布上画子弹 22 ctx.drawImage(m, this.mx, this.my); 23 this.my -= 20; 24 this.mx += this.n == 32 ? 3 : this.n == -32 ? -3 : 0; 25 if (this.my < -m.height) { // 如果子弹飞出画布,就标记为可移除 26 this.removable = true; 27 }; 28 }
构造敌机
1 /***********构造敌机********/ 2 var liveEnemy = []; // 用于存储画布上的所有敌机 3 4 function Enemy() { 5 this.n = Math.random() * 20; 6 this.enemy = null; // 保存敌机图片的数组 7 this.speed = 0; // 敌机的速度 8 this.lifes = 2; // 敌机的生命值 9 if (this.n < 1) { // 不同大小的敌机随机出现 10 this.enemy = enemy3[0]; 11 this.speed = 2; 12 this.lifes = 50; 13 } else if (this.n < 6) { 14 this.enemy = enemy2[0]; 15 this.speed = 4; 16 this.lifes = 10; 17 } else { 18 this.enemy = enemy1[0]; 19 this.speed = 6; 20 } 21 this.x = parseInt(Math.random() * (canvas.width - this.enemy.width)); 22 this.y = -this.enemy.height; 23 this.width = this.enemy.width; 24 this.height = this.enemy.height; 25 this.index = 0; 26 this.removable = false; 27 // 标识敌机是否狗带,若狗带就画它的爆炸图(也就是遗像啦) 28 this.die = false; 29 this.draw = function() { 30 // 处理不同敌机的爆炸图轮番上阵 31 if (this.speed == 2) { 32 if (this.die) { 33 if (this.index < 2) { this.index = 3; } 34 if (this.index < enemy3.length) { 35 this.enemy = enemy3[this.index++]; 36 } else { 37 this.removable = true; 38 } 39 } else { 40 this.enemy = enemy3[this.index]; 41 this.index == 0 ? this.index = 1 : this.index = 0; 42 } 43 } else if (this.die) { 44 if (this.index < enemy1.length) { 45 if (this.speed == 6) { 46 this.enemy = enemy1[this.index++]; 47 } else { 48 this.enemy = enemy2[this.index++]; 49 } 50 } else { 51 this.removable = true; 52 } 53 } 54 ctx.drawImage(this.enemy, this.x, this.y); 55 this.y += this.speed; // 移动敌机 56 this.hit(); //判断是否击中敌机 57 if (this.y > canvas.height) { // 若敌机飞出画布,就标识可移除(让你不长眼!) 58 this.removable = true; 59 } 60 } 61 this.hit = function() { //判断是否击中敌机 62 for (var i = 0; i < hullet.length; i++) { 63 var h = hullet[i]; 64 // 敌机与子弹的碰撞检测,自己体会吧 65 if (this.x + this.width >= h.mx && h.mx + h.width >= this.x && 66 h.my + h.height >= this.y && this.height + this.y >= h.my) { 67 if (--this.lifes == 0) { // 若生命值为零,标识为死亡 68 this.die = true; 69 // 计分 70 gameScore += this.speed == 6 ? 10 : this.speed == 4 ? 20 : 100; 71 } 72 h.removable = true; // 碰撞后的子弹标识为可移除 73 } 74 } 75 } 76 }
游戏的几种状态
1 /********定义游戏状态***********/ 2 const PHASE_DOWNLOAD = 1; 3 const PHASE_READY = 2; 4 const PHASE_LOADING = 3; 5 const PHASE_PLAY = 4; 6 const PHASE_PAUSE = 5; 7 const PHASE_GAMEOVER = 6; 8 /**********游戏当前状态************/ 9 var curPhase = PHASE_DOWNLOAD;
有了状态,我只需要起一个定时器,判断游戏的状态,绘制对应的帧就行;像这样:
/**********游戏主引擎*********/ function gameEngine() { switch (curPhase) { case PHASE_READY: pBg(); paintLogo(); break; case PHASE_LOADING: pBg(); load(); break; case PHASE_PLAY: pBg(); drawEnemy(); Hullet.drawHullet(); hero.draw(); break; case PHASE_PAUSE: drawPause(); break; } //requestAnimationFrame(gameEngine); } setInterval(gameEngine, 50);
完整代码在我的 GitHub。
本文完,有不对的地方,欢迎指正。I have a dream===Technical big bull.
H5 canvas 实现飞机大战游戏