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 = ‘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.

时间: 2024-10-29 10:46:53

H5 canvas 实现飞机大战游戏的相关文章

web版canvas做飞机大战游戏 总结

唠唠:两天的时间跟着做了个飞机大战的游戏,感觉做游戏挺好的.说是用html5做,发现全都是js.说js里一切皆为对象,写的最多的还是函数,都是函数调用.对这两天的代码做个总结,希望路过的大神指点一下,我对这个游戏的思路,可改进优化的代码. 先说一下游戏的基本内容: 打飞机(不要想歪了),有鼠标控制移动英雄机,子弹自动射击:敌机从上而下,有三种敌机: 先说下HTML代码(主要就是这一行): <canvas id="canFly" width="480" heig

基于Cocos2d-x-1.0.1的飞机大战游戏开发实例(下)

在飞机大战游戏开发中遇到的问题和解决方法: 1.在添加菜单时,我要添加一个有背景的菜单,需要在菜单pMenu中添加一个图片精灵,结果编译过了但是运行出错,如下图: 查了很多资料,调试了很长时间,整个人都要崩溃了. 最后发现引擎中CCMenu::itemForTouch函数中有遍历子节点的行为,但是循环中没有判断子节点类型是否为CCMenuItem.如图:码,这样一来,加入到pMenu中的图片精灵被当作菜单项取了出来使用,导致报错.老版本的果然又不完善的地方,整个人都不好了...果断修改引擎里的源

基于Cocos2d-x-1.0.1的飞机大战游戏开发实例(中)

接<基于Cocos2d-x-1.0.1的飞机大战游戏开发实例(上)> 三.代码分析 1.界面初始化 1 bool PlaneWarGame::init() 2 { 3 bool bRet = false; 4 do 5 { 6 CC_BREAK_IF(! CCLayer::init()); 7 8 _size = CCDirector::sharedDirector()->getWinSize(); 9 10 // 设置触摸可用 11 this->setIsTouchEnabled

基于Cocos2d-x-1.0.1的飞机大战游戏开发实例(上)

最近接触过几个版本的cocos2dx,决定每个大变动的版本都尝试一下.本实例模仿微信5.0版本中的飞机大战游戏,如图: 一.工具 1.素材:飞机大战的素材(图片.声音等)来自于网络 2.引擎:cocos2d-1.0.1-x-0.9.2 3.环境:vs2010 二.使用的类 1.游戏菜单界面类:PlaneWarMenu——派生自CCLayer类. 1 // 游戏菜单界面类 2 class PlaneWarMenu: public CCLayer 3 { 4 public: 5 virtual bo

用Swift语言和Sprite Kit复制微信飞机大战游戏

先上GitHub链接: https://github.com/songrotek/PlaneWar.git 接下来稍微讲解一下! 这个程序还有点Bug,见谅! 1 说明 游戏采用了Sprite kit最新的Per pixel for physic 技术,就是直接使用texture纹理作为sprite的physics body . 游戏的texture.atlas从别的打飞机项目中拷之并辛苦地分解了. 游戏编写借鉴了网上的objc代码! 2 游戏编写过程 添加背景-> 添加控制的飞机-> 添加发

微信飞机大战游戏开发

原文出自:方杰|http://fangjie.sinaapp.com/?p=366转载请注明出处 这学期上了一学期的Windows游戏开发课程,学期末的时候所以决定做一个微信飞机大战的小游戏. 不同于微信手机上的飞机大战,这是一个Win32平台下游戏.Win32项目,VS2008开发平台,利用我的老师写的TinyEngine微型游戏引擎开发. TinyEngine引擎的相关源码及介绍参见:https://github.com/JayFang1993/TinyEngine 飞机大战游戏的相关源码参

用canvas写飞机大战

1.老规矩,当我们开始做项目的时候,我们第一步就是要进行分析,当我们的游戏开始做的时候我们要把一整个游戏分成五个阶段来写: 五个阶段和我方飞机的生命值,还有游戏的得分情况如下: //游戏欢迎状态 const START=0; // 第二阶段:游戏加载状态 const LOADING=1; // 第三阶段:游戏运行状态 const RUNNING=2; // 第四阶段:游戏暂停阶段 const PAUSE=3; // 第五阶段:游戏结束阶段 const GAMEOVER=4; //定义游戏得分 v

飞机大战游戏思路及问题总结

在做这个游戏刚开始时,不知如何着手,思路很乱,不能统观全局.如隔靴搔痒,抓不住其中要点,窥不透真义.几天下来,在老师的引导下,基本完成了简单的功能,现在回顾一下过程中的心得以及遇到的问题,希望有所助益. 一.思路 1.页面布局 - 有两个界面,开始界面和游戏界面,两个大div:开始界面div有开始背景图片,有一个开始按钮,建议按钮包裹在一个div里,方便定位;游戏界面有其背景图片,上面有个得分div,另外还有暂停div包含继续按钮和重新开始按钮以及结果分数. - css div最好都是绝对定位,

cocos2d中分步实现飞机大战----游戏场景中背景的滚动

上一节说了场景的跳转,现在开始布置游戏游戏界面.在游戏的主界面,首先要有游戏背景,为了使GameScene的代码不至于太多,可以吧自己的背景进行封装,在GameScene中调用就好,飞机的正常飞行移动可以用北京的移动来实现.创建BackGround: background.h: #include "cocos2d.h" USING_NS_CC; class background:public Node{ public: CREATE_FUNC(background); bool ini