zoukankan      html  css  js  c++  java
  • 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.

  • 相关阅读:
    css文本及文本装饰
    css尺寸常用样式
    了解css的两大特性
    css长度单位及字体
    css颜色
    css选择器详解
    了解css
    html行级元素与块级元素以及meta标签的使用
    了解html表单
    html图片和html实体
  • 原文地址:https://www.cnblogs.com/xshuai/p/6686089.html
Copyright © 2011-2022 走看看