使用canvas编写飞机大战游戏

使用canvas编写飞机大战小游戏

本文章讲述如何使用canvas实现一个飞机大战的小游戏,游戏我取名为fight-AO(点击查看源码)实现可以发射子弹、击败怪兽、自动生成怪兽…怪兽名字为AO,击败AO的叫做Superman。点击试玩



前言

在了解如何编写游戏过程时,你需要知道vue框架element ui帮助你快速快速开发。


一、组件版本

├─┬ @vue/cli-plugin-babel@4.5.13
│ └─┬ @vue/babel-preset-app@4.5.13
│   └── vue@2.6.14 deduped
├─┬ element-ui@2.15.3 -> ./node_modules/_element-ui@2.15.3@element-ui
│ └── vue@2.6.14 deduped
└── vue@2.6.14

二、项目结构

.
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public						#开放的资源
│   ├── favicon.ico
│   ├── fight-AO
│   │   ├── AO.png
│   │   └── superman.png
│   └── index.html
└── src							#源文件
    ├── App.vue
    ├── assets					#项目资源文件
    │   └── logo.png
    ├── components				#vue组件
    │   └── fight-AO.vue		#本文章讲解的文件
    ├── main.js					#项目主配置
    └── router					#vue组件路由
        └── router.js

三、代码&思路讲解

以下讲解对编写游戏过程的讲解,不会深入剖析底层代码。

1. 使用canvas

1.1 代码

<canvas id="canvas"></canvas>

//样式
<style scoped>
	#canvas {
	  margin: 0;
	  background-image: url("../../public/fight-AO/sky.jpeg");
	}
</style>

//获取canvas
this.canvas = document.getElementById('canvas');
this.ctx = this.canvas.getContext('2d');
//宽和高指定为页面布局大小
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight - 50;



1.2 知识点


2. 移动超人

2.1 代码

	
	//超人外形
    this.supermanImg.src = '/fight-AO/superman.png';
    
    //超人初始位置
    this.supermanX = (this.canvas.width - this.supermanW) / 2;
    this.supermanY = (this.canvas.height - this.supermanH) / 1.2;

    //外形加载完成后设置superman初始位置
    this.supermanImg.onload = () => {
      this.supermanMove();
    }

    //监听走位
    window.onkeydown = this.keyDown;
    window.ontouchmove = this.touchmove;
    
    /**
     * 移动超人
     */
    supermanMove() {
      this.moveArr.push({x: this.supermanX, y: this.supermanY, w: this.supermanW, h: this.supermanH})
      //如果有移动就删除指定位置
      if (this.moveArr.length > 1) {
        let m = this.moveArr[this.moveArr.length - 2];
        this.ctx.clearRect(m.x, m.y, m.w, m.h);
      }
      let m = this.moveArr[this.moveArr.length - 1];
      this.ctx.drawImage(this.supermanImg, m.x, m.y, m.w, m.h);

      //清除以前步数
      for (let i = 0; i < this.moveArr.length - 3; i++) {
        delete this.moveArr[i];
      }
    }
    /**
     * 键盘事件
     */
    keyDown(event) {
      if (!this.checkActionStartStatus()) return;
      switch (event.keyCode) {  // 获取当前按下键盘键的编码
        case 32 :  // 按空格暂停
          break;
        case 37 :  // 按下左箭头键,向左移动
          this.supermanX -= this.speed;
          break;
        case 39 :  // 按下右箭头键,向右移动
          this.supermanX += this.speed;
          break;
        case 38 :  // 按下上箭头键,向上移动
          this.supermanY -= this.speed;
          break;
        case 40 :  // 按下下箭头键,向下移动
          this.supermanY += this.speed;
          break;
          // default:
          //     return;
      }
      //是否超出边界
      if (this.supermanX >= this.canvas.width - (this.supermanW / 2)) {
        this.supermanX -= this.speed;
      } else if (this.supermanX + (this.supermanW / 2) <= 0) {
        this.supermanX += this.speed;
      } else if (this.supermanY >= this.canvas.height - this.supermanW) {
        this.supermanY -= this.speed;
      } else if (this.supermanY <= 0) {
        this.supermanY += this.speed;
      }
      this.supermanMove();
    },
    /**
     * 手指摁下
     * @param event
     */
    touchmove(event) {
      if (!this.checkActionStartStatus()) return;
      let moveX = event.changedTouches[0].pageX;
      let moveY = event.changedTouches[0].pageY;
      this.supermanX = moveX;
      this.supermanY = moveY;
      this.supermanMove();
    }

2.2 知识点

  • 通过速度this.speed改变超人移动的速度,只有在键盘事件有用,玩家操作上下左右分别对应y-speed, y+speed, x-speed, x+speed。改变this.supermanX,this.supermanY的值超人就可以轻易的移动。
  • supermanMove()函数封装了记录this.moveArr超人位置,this.ctx.clearRect清除上一次显示位置,this.ctx.drawImage画出当前移动位置,清除历史移动delete this.moveArr[i];

3. 发射炮弹cannonball

3.1 代码


    //启动炮弹
    setInterval(() => {
      if (this.checkActionStartStatus()) {
        this.cannonball();
        this.upup = 10;
      }
    }, 300)

    /**
     * 炮弹
     */
    cannonball() {
      let cannonballArr = []

      //先把y画出来 当前y-upup直到小于0 y越小就越往上 直到小于-AO的高度
      while (this.supermanY - this.upup > -this.aoHeight) {
        this.upup += this.cannonballSpace;//加上每个炮弹的间距
        //记录当前这组炮弹发射的轨迹
        cannonballArr.push({x: this.supermanX + (this.supermanW / 2), y: this.supermanY - this.upup});
      }

      let index = 0;//炮弹位置
      let clearCannonball = 0;//清除多余炮弹
      let cannonballNum = this.cannonballNum;//炮弹数量
      //通过定时器实现炮弹动起来的效果
      let interval = setInterval(() => {
        //炮弹发完了 只有发射一次炮弹才会走这个判断 避免浪费资源
        if (cannonballArr.length === 0) {
          clearInterval(interval);
          return;
        }

        //到顶了炮弹看不见了
        if (cannonballNum >= cannonballArr.length) {
          cannonballNum = cannonballArr.length - 1;
        }

        //清除历史炮弹 
        // console.log(index + '===' + cannonballNum + '====' + clearCannonball + '====' + cannonballArr.length)
        if (index >= cannonballNum && clearCannonball < cannonballArr.length) {
          let arrElement = cannonballArr[clearCannonball];
          this.ctx.clearRect(arrElement.x, arrElement.y + this.cannonballHeight,
              this.cannonballWidth + this.cannonballWidth / 2,
              this.cannonballSpace)
          clearCannonball++;
        }

        //炮弹发完了
        if (index >= cannonballArr.length - 1) {
          if (clearCannonball > cannonballArr.length - 1) clearInterval(interval);
        } else {
          //发射炮弹
          let arrElement = cannonballArr[index];
          if (!arrElement) return;
          //炮弹的样式
          this.ctx.fillStyle = "rgb(200,100,200)"
          this.ctx.fillRect(arrElement.x, arrElement.y, this.cannonballWidth, this.cannonballHeight)

          //清除啊哦 这段判断主要是消灭AO
          if (this.aoArr.length > 0) {
            // let arrElement = cannonballArr[cannonballArr.length - 1 - index];
            //查找啊哦的位置
            let aoObj = this.aoArr.find(o =>
                //把区间放大
                (o.x + this.aoWidth) >= arrElement.x && (o.x) <= arrElement.x
                && o.y + this.aoWidth >= arrElement.y && o.y - this.aoHeight <= arrElement.y
            );

            if (aoObj) {
              this.aoDieNum++;
              // console.log(index + '===' + arrElement.x + '===' + arrElement.y)
              this.ctx.clearRect(aoObj.x, aoObj.y, this.aoWidth, this.aoHeight)
              aoObj.x = 1000;
              aoObj.y = 1000;
              this.resetAO();
            }
          }
          index++;
        }
      }, 30);
    }

3.2 知识点

  • this.upup一组炮弹移动间隙,通过它来改变y值的变化让AO动起来
  • this.cannonballSpace每个炮弹之间的空间,让炮弹看起来更加有节奏
  • this.cannonballNum每次发射的炮弹数量

4. AO小怪兽

4.1 代码

    this.aoImg1.src = "/fight-AO/AO01.png"
    this.aoImg2.src = "/fight-AO/AO02.png"
    this.aoImg3.src = "/fight-AO/AO03.png"
    this.aoImg4.src = "/fight-AO/AO04.png"
    this.aoImg5.src = "/fight-AO/AO05.png"

    //初始化啊哦
    this.AO();

    //开启AO移动速度 50ms移动一次
    setInterval(() => {
      //检查游戏是否开始 如果以开始就判断超人是否已触碰到AO游戏结束
      if (this.checkActionStartStatus()) {
        //查找啊哦的位置
        let aoObj = this.aoArr.find(o =>
            //把区间放大
            o.x + (this.aoWidth / 2) >= this.supermanX && o.x - (this.aoWidth * 1.1) <= this.supermanX
            && o.y + this.aoHeight >= this.supermanY && o.y - this.aoHeight <= this.supermanY
        );
        //是否已触碰到超人
        if (aoObj) {
          //游戏暂停
          this.actionPause();
          //提示游戏结束
          this.$alert('游戏结束💀重新开始', '提示', {
            confirmButtonText: '确定',
            showClose: false,
            customClass: 'hintAlert',
            callback: () => {
              location.reload();
            }
          });
        }
        //重置AO 让它开起来是在移动的
        this.resetAO();
      }
    }, 50);


    // 被消灭者【啊哦】
    AO() {
      let AOAO = setInterval(() => {
        this.setDataStatus = false;
        if (!this.checkActionStartStatus()) return;
        //AO是否已经全部出现
        if (this.aoNum < 1) {
          clearInterval(AOAO);
          //ao已被初始化还完成可以设置
          this.setDataStatus = true;
          return;
        }
        //随机出现位置
        let numX = Math.round(Math.random() * (this.canvas.width - 5)) + 5;
        let numY = -this.aoHeight;//Math.round(Math.random() * 50)
        // console.log(numX + '==' + numY)
        let random = Math.round(Math.random() * 5);
        this.drawAO(random, numX, numY);
        this.aoArr.push({x: numX, y: numY, random: random})
        this.aoNum--;
      }, 500)
    }
    /**
     * 画啊哦 在任意一个case画一个AO 每个case的AO样式是不同的
     */
    drawAO(random, numX, numY) {
      switch (random) {
        case 1:
          this.ctx.drawImage(this.aoImg1, numX, numY, this.aoWidth, this.aoHeight)
          break;
        case 2:
          this.ctx.drawImage(this.aoImg2, numX, numY, this.aoWidth, this.aoHeight)
          break;
        case 3:
          this.ctx.drawImage(this.aoImg3, numX, numY, this.aoWidth, this.aoHeight)
          break;
        case 4:
          this.ctx.drawImage(this.aoImg4, numX, numY, this.aoWidth, this.aoHeight)
          break;
        case 5:
          this.ctx.drawImage(this.aoImg5, numX, numY, this.aoWidth, this.aoHeight)
          break;
      }
    },
    /**
     *让AO往左移动还是右
     * @param i ao位置
     * @param type 1加 右 , 2减 左
     */
    leftOrRight(i, type) {
      switch (type) {
        case 1:
          this.aoArr[i].x += Math.round(Math.random() * 2)
          break;
        case 2:
          this.aoArr[i].x -= Math.round(Math.random() * 2)
          break;
      }
    },
    /**
     * 1、补全未被消灭的啊哦 2、AO互相之间触碰他是被误伤的 3、让啊哦持续动起来
     */
    resetAO() {
      //先清当前位置的AO
      for (let i = 0; i < this.aoArr.length; i++) {
        this.ctx.clearRect(this.aoArr[i].x, this.aoArr[i].y, this.aoWidth, this.aoHeight)
      }
      //重新渲染新的AO
      for (let i = 0; i < this.aoArr.length; i++) {
        //改变x轴移动位置 计算x轴的位置占总宽的多少
        let xPosition = (this.aoArr[i].x + this.aoWidth) / this.canvas.width;
        //随机以下往左还是右
        let type = Math.round(Math.random() * 2);
        //定义了几种简单的移动规则
        if (xPosition > 0 && 0.1 > xPosition) {
          this.leftOrRight(i, type);
        } else if (xPosition > 0.1 && 0.2 > xPosition) {
          this.leftOrRight(i, type);
        } else if (xPosition > 0.2 && 0.3 > xPosition) {
          this.leftOrRight(i, type);
        } else if (xPosition > 0.3 && 0.5 > xPosition) {
          this.leftOrRight(i, type);
        } else if (xPosition > 0.5 && 0.7 > xPosition) {
          this.leftOrRight(i, 2);
        } else if (xPosition > 0.7 && 1 > xPosition) {
          this.leftOrRight(i, 2);
        } else {
          this.leftOrRight(i, 2);
        }
        //让AO在页面上显示出来
        this.drawAO(this.aoArr[i].random, this.aoArr[i].x, this.aoArr[i].y++)
      }
    }

4.2 知识点

  • this.aoNum AO生成数量
  • resetAO() 函数可以更友好地显示重叠在一起的AO不被互相清除,通过先清空当前显示的AO,在计算下一个位置的AO。AO()函数只有在第一次初始化AO的时候才是有用的。虽然两个函数都是可以生成AO的逻辑,但是他们虽执行的业务逻辑是不一样的。AO()初始化,resetAO()改变数据。
  • AO的行动轨迹只是一个和简单的区域划分,这个地方可以提前生成每一个AO的行动轨迹不应该每次都要重新判断AO下一步的位置应该是哪。TODO

四、总结

以上就是使用canvas来实现这个游戏的主要代码逻辑,使用到的知识都是通过查看canvas官网上的demo来进行学习,主要用的函数有ctx.fillRect绘制矩形,ctx.drawImage绘制图像,ctx.clearRect清除指定矩形区域(让清除部分完全透明),监听键盘事件,触屏摁下事件。这个游戏设计想法有参考微信小游戏一些的实现,AO的命名想法来源是AI Object,这个怪兽可以是任意一个对象通过AI让他赋予生命。一种简单的2d游戏还是可以通过js来实现的,在编写的过程中很好玩。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的飞机大战游戏的示例代码: index.jsp 文件: ``` <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>飞机大战</title> <style> canvas { border: 1px solid #000; } </style> </head> <body> <canvas id="gameCanvas"></canvas> <script src="game.js"></script> </body> </html> ``` game.js 文件: ``` // 定义游戏画布的大小 var canvasWidth = 480; var canvasHeight = 680; // 创建游戏画布 var canvas = document.getElementById('gameCanvas'); canvas.width = canvasWidth; canvas.height = canvasHeight; // 获取 2D 渲染上下文 var ctx = canvas.getContext('2d'); // 定义游戏角色的属性 var player = { x: canvasWidth / 2, y: canvasHeight - 50, width: 50, height: 50, speed: 5, image: new Image() }; var enemies = []; // 加载游戏角色和敌人的图片资源 player.image.src = 'player.png'; var enemyImage = new Image(); enemyImage.src = 'enemy.png'; // 定义游戏主循环函数 function gameLoop() { // 清空画布 ctx.clearRect(0, 0, canvasWidth, canvasHeight); // 绘制玩家角色 ctx.drawImage(player.image, player.x - player.width / 2, player.y - player.height / 2, player.width, player.height); // 移动敌人角色,并绘制 for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; enemy.y += enemy.speed; ctx.drawImage(enemyImage, enemy.x - enemy.width / 2, enemy.y - enemy.height / 2, enemy.width, enemy.height); } // 检测碰撞 checkCollision(); // 更新游戏状态 updateGameState(); // 循环调用主循环函数 requestAnimationFrame(gameLoop); } // 定义更新游戏状态的函数 function updateGameState() { // 根据玩家输入更新角色位置 if (keyState.left) { player.x -= player.speed; } if (keyState.right) { player.x += player.speed; } // 添加新的敌人角色 if (Math.random() < 0.05) { var enemy = { x: Math.random() * canvasWidth, y: -50, width: 50, height: 50, speed: 3 }; enemies.push(enemy); } } // 定义检测碰撞的函数 function checkCollision() { for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (Math.abs(enemy.x - player.x) < (enemy.width + player.width) / 2 && Math.abs(enemy.y - player.y) < (enemy.height + player.height) / 2) { gameOver(); break; } } } // 定义游戏结束的函数 function gameOver() { alert('游戏结束!'); // 重新加载页面,重新开始游戏 location.reload(); } // 定义键盘输入状态的对象 var keyState = {}; // 监听键盘按下事件 document.addEventListener('keydown', function(event) { if (event.keyCode === 37) { keyState.left = true; } if (event.keyCode === 39) { keyState.right = true; } }); // 监听键盘释放事件 document.addEventListener('keyup', function(event) { if (event.keyCode === 37) { keyState.left = false; } if (event.keyCode === 39) { keyState.right = false; } }); // 启动游戏主循环 gameLoop(); ``` 注意:以上代码仅为示例代码,具体的实现方式还需要根据游戏的需求进行调整。同时,需要准备好 player.png 和 enemy.png 两张图片资源,并且将它们放在与 game.js 文件同一目录下。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值