H5 canvas 實現飛機大戰遊戲

首先看幾張效果圖:html

上面三張圖分別對應遊戲的三種狀態 ready,play,pause。體驗一下git

先介紹一下canvas 畫圖的原理,在這個遊戲中的背景,飛機,子彈以及飛機被擊中爆炸的效果都是一張張的圖片,經過canvas的 drawImage() 函數把這一幀須要的全部圖片按其所在的位置(座標)畫到畫布上,固然有時候也須要畫些文本,好比左上角的得分;而後接着畫下一幀,同時改變飛機和子彈的位置;畫下一幀以前必定要清除畫布(經過這個函數 clearRect(x,  y, width, height)),否則就是下圖的效果啦:

辣眼睛!!!
不過在本例中由於每幀都要從新畫上背景圖,背景圖又是填滿整個畫布的,因此畫背景圖時就等於把上一幀所有覆蓋了,也就至關於清除畫布了。github

下面咱們開始聊實現的細節:canvas

加載須要的圖片

在讓遊戲跑起來以前要先把須要的圖片加載進來,相似:

代碼以下:數組

 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 = '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();

 

其中有處理圖片分類和加載進度的問題,代碼有些冗餘。dom

讓背景動起來

從上面的遊戲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的碰撞檢測和生命值,因此英雄無敵!!!哈哈哈哈!!!
然並卵,我已經寫不下去了;但是,堅持就是勝利呀;好吧,繼續!this

構造子彈

 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;

 

有了狀態,我只須要起一個定時器,判斷遊戲的狀態,繪製對應的幀就行;像這樣:spa

/**********遊戲主引擎*********/
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);

 

完整代碼在個人 GitHubprototype

本文完,有不對的地方,歡迎指正。I have a dream===Technical big bull.

相關文章
相關標籤/搜索