一,概述

此小项目,是用来练习HTML5的canvas的编程运用。在这个项目中,我们需要创建一个可运行的网页小游戏,开发此小游戏并不难,大概如下图所示:

在整个游戏的运行中,总共要分为5个状态(state)去实现,分别是首页(START),加载中(STARTING),游戏中(RUNNING),暂停(PAUSE)和游戏结束(GAME_OVER),运用一个计时器在网页的canvas画布上画出对应的图片,再通过鼠标的事件来触发并转换状态,就可以实现游戏的运行了。大体代码框架:

<div id="stage" style="width:480px;height:650px;margin:0px auto;"><canvas id="canvas" width="480" height="650"></canvas>
</div>
<script>//定义宽和高var WIDTH=480,HEIGHT=650;//定义状态var START = 0;var STARTING = 1;var RUNNING = 2;var PAUSE = 3;var GAME_OVER = 4;/*** state表示游戏的状态* 取值必须为以上五种之一*/var state = START;//获取数据var canvas = document.getElementById("canvas");ctx = canvas.getContext("2d");//创建图像对象用来表示天空、英雄、敌人、版权//为canvas添加事件(onclick,onmousemove,onmoseout)//数据对象//业务对象//创建业务对象//定义计时器,固定刷新频率为 1000 / 100setInterval(function(){switch(state){case START://首页break;case STARTING://加载中break;case RUNNING://游戏中break;case PAUSE://暂停break;case GAME_OVER://游戏结束break;}},1000/100);
</script>

二,首页

编写好大体框架后,接下来,我们要逐步逐步完成各个状态的代码。首页很简单的,包括两元素,一个是背景天空的图片(持续由上往下循环滚动),一个是飞机大战的图标(固定在网页正中间)。如下图所示:

对应代码如下,将它补充到上述框架中:

<script>//创建图像对象用来表示天空var bg = new Image();bg.src="img/background.png";//创建图像对象用来表示图标var copyright = new Image();copyright.src="img/shoot_copyright.png";//数据对象var SKY = {image:bg,width:480,height:650,speed:20};var LOADING = {frames:l,width:186,height:38,x:0,y:HEIGHT-38,speed:5};//业务对象/*** 天空的业务对象* config:表示天空的数据对象*/var Sky = function(config){this.bg = config.image; //设置背景图像this.width=config.width;this.height = config.height;this.speed = 1000 / config.speed;this.x1 = 0;this.y1 = 0;this.x2 = 0;this.y2 = -this.height;this.lastTime = 0; //上一次执行动作时间的毫秒数/*** 移动背景纵坐标*/this.step = function(){//判断是否到达天空移动的时间//获取当前时间的毫秒数var currentTime = new Date().getTime();if(currentTime - this.lastTime >= this.speed){// y1++ , y2++this.y1++;this.y2++;this.lastTime = new Date().getTime();} //判断y1、y2 是否超出范围if(this.y1 >= this.height){this.y1 = -this.height;}if(this.y2 >= this.height){this.y2 = -this.height;}}/*** 绘制天空图像*ctx : canvas 绘图上下文*/this.paint = function(ctx){ctx.drawImage(this.bg,this.x1,this.y1);ctx.drawImage(this.bg,this.x2,this.y2);}}//创建业务对象var sky = new Sky(SKY);//以下代码放入计时器中case START://空在移动sky.step();sky.paint(ctx);//绘制copyrightvar x = (WIDTH - copyright.naturalWidth) / 2;var y = (HEIGHT - copyright.naturalHeight) / 2;ctx.drawImage(copyright,x,y);break;
<script>

三,加载中

加载中的状态相对于首页就复杂一点了,加载状态是由首页进行鼠标单击切换而来,加载完毕即进入游戏中的状态,同样是天空的背景滚动(天空全程都在滚动,即使暂停或者游戏结束页在滚动),但是,下面需要有一架飞机飞过表示加载进度,这由4幅图切换而成。如下图所示:

对应代码如下所示:

<script>//创建数组存储加载的四个图片对象 var l = [];l[0] = new Image();l[0].src="img/game_loading1.png";l[1] = new Image();l[1].src="img/game_loading2.png";l[2] = new Image();l[2].src="img/game_loading3.png";l[3] = new Image();l[3].src="img/game_loading4.png"; //为canvas添加事件(onclick,onmousemove,onmoseout)canvas.οnclick=function(){if(state == START){state = STARTING;}} //数据对象 var LOADING = {frames:l,width:186,height:38,x:0,y:HEIGHT-38,speed:5}; //业务对象 /*** 加载的业务对象* config:表示加载的数据对象*/var Loading = function(config){this.speed = 1000 / config.speed;this.lastTime = 0;this.frame=null;this.frameIndex = 0;//更换loading图像this.step = function(){var currentTime = new Date().getTime();if(currentTime - this.lastTime >= this.speed){//获取不同的图像config.frames中的元素给framethis.frame = config.frames[this.frameIndex];this.frameIndex ++;if(this.frameIndex >= 4){//更新状态state = RUNNING;}this.lastTime = new Date().getTime();}}/*** 绘制不同的图像到canvas上*/this.paint = function(ctx){ctx.drawImage(this.frame,config.x,config.y);}} //创建业务对象var loading = new Loading(LOADING); //以下代码放入计时器中case STARTING://准备开始sky.step();sky.paint(ctx);loading.step();loading.paint(ctx);break;
</script>

三,游戏中

游戏中的状态是最复杂的,里面需要有滚动的天空,我们自己控制的飞机(跟随鼠标位置移动),子弹(自动从飞机头发射),敌方飞机(随机从上方生成,不会攻击,有三种类型,按大小分为大中小),分数(score)以及生命值(life)。大致如下图所示:

对应代码如下所示:

<script>//创建英雄图像数组var h = [];h[0] = new Image();h[0].src="img/hero1.png";h[1] = new Image();h[1].src="img/hero2.png";h[2] = new Image();h[2].src="img/hero_blowup_n1.png";h[3] = new Image();h[3].src="img/hero_blowup_n2.png";h[4] = new Image();h[4].src="img/hero_blowup_n3.png";h[5] = new Image();h[5].src="img/hero_blowup_n4.png";//创建子弹图像var b = new Image();b.src="img/bullet1.png";//创建小飞机图像数组var e1 = [];e1[0] = new Image();e1[0].src="img/enemy1.png";e1[1] = new Image();e1[1].src="img/enemy1_down1.png";e1[2] = new Image();e1[2].src="img/enemy1_down2.png";e1[3] = new Image();e1[3].src="img/enemy1_down3.png";e1[4] = new Image();e1[4].src="img/enemy1_down4.png";//创建中型飞机图像数组var e2 = [];e2[0] = new Image();e2[0].src="img/enemy2.png";e2[1] = new Image();e2[1].src="img/enemy2_down1.png";e2[2] = new Image();e2[2].src="img/enemy2_down2.png";e2[3] = new Image();e2[3].src="img/enemy2_down3.png";e2[4] = new Image();e2[4].src="img/enemy2_down4.png";//创建大型飞机的图像数组var e3 = [];e3[0] = new Image();e3[0].src="img/enemy3_n1.png";e3[1] = new Image();e3[1].src="img/enemy3_n2.png";e3[2] = new Image();e3[2].src="img/enemy3_down1.png";e3[3] = new Image();e3[3].src="img/enemy3_down2.png";e3[4] = new Image();e3[4].src="img/enemy3_down3.png";e3[5] = new Image();e3[5].src="img/enemy3_down4.png";e3[6] = new Image();e3[6].src="img/enemy3_down5.png";e3[7] = new Image();e3[7].src="img/enemy3_down6.png";/***鼠标移动事件*处理 hero与鼠标的位置*/canvas.onmousemove = function(e){var x = e.offsetX;var y = e.offsetY;hero.x = x - HERO.width / 2;hero.y = y - HERO.height / 2;}//数据对象var HERO = {frames:h,baseFrameCount:2,width:99,height:124,speed:20};var BULLET = {image:b,width:9,height:21};var E1 = {type:1,score:1,frames:e1,baseFrameCount:1,life:1,minSpeed:160,maxSpeed:180,width:57,height:51};var E2 = {type:2,score:5,frames:e2,baseFrameCount:1,life:5,minSpeed:120,maxSpeed:150,width:69,height:95};var E3 = {type:3,score:20,frames:e3,baseFrameCount:2,life:20,speed:80,width:169,height:258};//保存由hero发射的所有子弹var bullets = [];//保存生成的敌机var enemies = [];//业务对象var Enemy = function(config){this.down = false;//是否播放爆破状态,默认为否 this.canDelete = false;//是否删除当前飞机,默认为否this.life = config.life;//敌人的生命力this.score = config.score;//分数this.frames = config.frames;//图像列表this.frame=null;//当前显示的图像this.frameIndex = 0;//当前显示的图像索引累加值this.baseFrameCount=config.baseFrameCount;this.width = config.width;this.height = config.height;//横纵坐标this.x = Math.ceil(Math.random()*(WIDTH - config.width));this.y = -config.height;this.type = config.type;this.speed = 0;if(config.minSpeed && config.maxSpeed){this.speed = 1000 / (Math.random() * (config.maxSpeed - config.minSpeed) + config.minSpeed);}else {this.speed = 1000 / config.speed;}this.lastTime = 0;/*** 检查时间是否到期*/this.timeInterval = function(){var currentTime = new Date().getTime();if(currentTime - this.lastTime >= this.speed){this.lastTime = new Date().getTime();return true;}return false;}this.step=function(){if(this.timeInterval()){if(this.down){//播放爆破图像//已经确定this.frameIndex = this.baseFrameCount;if(this.frameIndex ==this.frames.length){this.canDelete = true;} else {this.frame = this.frames[this.frameIndex];this.frameIndex ++;}}else{//播放基本图像this.frame = this.frames[this.frameIndex % this.baseFrameCount];this.frameIndex ++;//飞机移动this.move();}}}this.move = function(){//飞机移动this.y ++;}this.paint = function(ctx){//绘制飞机图像ctx.drawImage(this.frame,this.x,this.y);}/*** 判断当前飞机对象是否超出canvas边界*/this.outOfBounds = function(){if(this.y > HEIGHT){return true;} return false;}/*** 判断敌人是否与其他物体碰撞* c:c可以是英雄,可以是子弹*/this.hit = function(c){//c的中心点坐标var cX = c.x + c.width / 2;var cY = c.y + c.height / 2;var leftStart = this.x - c.width / 2;var leftEnd = this.x + this.width + c.width / 2;var topStart = this.y - c.height / 2;var topEnd = this.y + this.height + c.height / 2;var result = leftStart < cX && cX < leftEnd && topStart < cY && cY < topEnd;return result;}/*** 当敌人飞机与其他元素碰撞时的操作方法*/this.duang = function(){//生命的减少this.life --;if(this.life == 0){//切换到爆破状态this.down = true;score += this.score;this.frameIndex = this.baseFrameCount;}}}/*** 子弹对象*/var Bullet = function(config,x,y){this.width = config.width;this.height = config.height;this.frame = config.image;this.x = x;this.y = y;this.canDelete = false;//是否删除子弹,默认为否this.move = function(){this.y -= 2;}this.paint = function(ctx){ctx.drawImage(this.frame,this.x,this.y);}this.outOfBounds = function(){return this.y < 0-this.height;}/*** 子弹与敌人飞机碰撞时所做的操作*/this.duang = function(){this.canDelete = true;}}/*** 创建飞机业务对象*/var Hero = function(config){this.frames = config.frames;this.frameIndex = 0;this.baseFrameCount=config.baseFrameCount;this.width=config.width;this.height=config.height;this.speed=1000/config.speed;this.lastTime = 0;this.x = (WIDTH - this.width) / 2;this.y = HEIGHT - this.height - 30;this.down=false;this.canDelete = false;this.step = function(){var currentTime = new Date().getTime();if(currentTime - this.lastTime >= this.speed){if(this.down){//爆破状态if(this.frameIndex == this.frames.length){this.canDelete = true;}else {this.frame = this.frames[this.frameIndex];this.frameIndex ++;}}else{//正常状态this.frame = this.frames[this.frameIndex % this.baseFrameCount];this.frameIndex ++;this.lastTime = new Date().getTime();}}}this.paint = function(ctx){ctx.drawImage(this.frame,this.x,this.y);}this.shootLastTime=0;//发射子弹的间隔this.shootInterval = 200;//处理子弹的发射this.shoot = function(){var currentTime = new Date().getTime();if(currentTime - this.shootLastTime >= this.shootInterval){//到达时间间隔,可以发射子弹var bullet = new Bullet(BULLET,this.x+45,this.y);bullets[bullets.length] = bullet;//console.log("子弹数量:"+bullets.length);this.shootLastTime = new Date().getTime();}}/*** 英雄与敌人碰撞后的操作*/this.duang = function(){this.down = true;this.frameIndex = this.baseFrameCount;}}//创建业务对象var hero = new Hero(HERO);/*** 检查 敌人是否与子弹、英雄碰撞*/function checkHit(){for(var i=0;i<enemies.length;i++){var enemy = enemies[i];if(enemy.down || enemy.canDelete){continue;}//与子弹相比较for(var j=0;j<bullets.length;j++){var bullet = bullets[j];//进行比较if(enemy.hit(bullet)){enemy.duang();bullet.duang();}}//判断与英雄比较if(enemy.down || enemy.canDelete){continue;}if(enemy.hit(hero)){enemy.duang();hero.duang();}}}//删除多余组件function deleteComponent(){//删除超出下边界的小飞机for(var i=0;i<enemies.length;i++){if(enemies[i].outOfBounds() || enemies[i].canDelete){enemies.splice(i,1);}}//删除超出上边界的子弹for(var i=0;i<bullets.length;i++){if(bullets[i].outOfBounds() || bullets[i].canDelete){bullets.splice(i,1);}}//判断英雄是否需要被删除if(hero.canDelete){life --;//减少生命if(life == 0){//GAME_OVERstate = GAME_OVER;}else{hero = new Hero(HERO);}}}//创建敌人飞机的数据var lastTime = new Date().getTime();var interval = 800;/***根据指定时间差创建不同类型的敌人飞机*将创建好的飞机保存进 enemies 数组中*/function componentEnter(){var currentTime = new Date().getTime();if(currentTime - lastTime >= interval){var n = Math.floor(Math.random()*10);if(n >= 0 && n <= 7){//创建小型飞机enemies[enemies.length]=new Enemy(E1);}else if(n == 8){//创建中型飞机enemies[enemies.length]=new Enemy(E2);} else {//创建大型飞机//如果数组中第一个元素不是大型飞机,则创建一个,并且放在第一个位置处,其他的飞机位置后移if(enemies[0].type != 3){enemies.splice(0,0,new Enemy(E3));}}lastTime = new Date().getTime();}}/*** 绘制各个组件*/function paintComponent(){//绘制子弹for(var i=0;i<bullets.length ; i ++){var bullet = bullets[i];bullet.paint(ctx);}//绘制所有敌人小飞机for(var i=0;i<enemies.length ; i ++){enemies[i].paint(ctx);}//将绘制hero的方法移动至此hero.paint(ctx);ctx.font = "20px 微软雅黑";ctx.fillText("SCORE:"+score,10,20);ctx.fillText("LIFE:"+life,400,20)}/*** 让所有的组件动起来(更新y坐标)*/function stepComponent(){for(var i=0;i<bullets.length ; i ++){bullets[i].move();}//移动所有敌人小飞机for(var i=0;i<enemies.length ; i ++){enemies[i].step();}}//以下代码放入计时器中case RUNNING://游戏进行sky.step();sky.paint(ctx);hero.step();hero.paint(ctx);hero.shoot();checkHit();//添加新组件(敌人小飞机)componentEnter();stepComponent();deleteComponent();//绘制所有的组件paintComponent();break;
</script>

我方飞机(hero)由2个正常状态与4个爆炸状态的图片组成,子弹(b)由单独一张图片表示,小飞机(e1)由一张正常状态和4张爆炸状态组成, 中型飞机(e2)由一张正常状态和4张爆炸状态图片组成,大型飞机(e3)由两张正常状态与6张爆炸状态图片组成。

***************************hero的位置问题***************************

在获取鼠标位置时,如果直接将鼠标位置添加给图片,会发现图片在鼠标的右下方,并不是图片的正中间是鼠标,所有,我们还有在获取到鼠标位置后,在横纵轴的方向上各自减去hero的图片大小的一半,将图片向左上移动。

******************敌机与子弹和英雄的碰撞问题**********************

我们可以将碰撞问题简化,简化为两个矩形,如果对方的顶点进入了对方矩形范围的区域,则为发生了碰撞。这时,我们可以以敌机(e)为参考对象,计算出敌机与对方的碰撞范围(横纵坐标的范围值)与对方的中心坐标进行比较,如果我方飞机或者子弹的中心坐标进入了敌机的碰撞范围,则触发爆炸效果。大致如下图所示:

四,暂停

游戏中的状态做完,接下来就简单多了。暂停状态,只要鼠标离开了canvas的范围,就游戏暂停,敌机,子弹,hero都停止,天空继续动,并且在中间位置出现暂停的图标,鼠标移回canvas范围,则又继续游戏。效果如下所示:

对应代码如下:

<script>//创建图像对象用来表示暂停var pause = new Image();pause.src="img/game_pause_nor.png";/***鼠标移动事件*处理 hero与鼠标的位置*/canvas.onmouseout = function(e){if(state == RUNNING){state = PAUSE;}}canvas.onmouseover = function(e){if(state == PAUSE){state = RUNNING;}}//以下代码放入计时器中case PAUSE://暂停sky.step();sky.paint(ctx);paintComponent();ctx.drawImage(pause,(WIDTH-pause.width)/2,(HEIGHT-pause.height)/2);break;
</script>

五,游戏结束

游戏结束只需要所有元素都停止,并显示GAME_OVER即可,所以直接改计时器的内容。如下所示:

<script>case GAME_OVER://游戏结束ctx.font = "bold 24px 微软雅黑";var width =ctx.measureText("GAME_OVER").width;ctx.fillText("GAME_OVER",(WIDTH-width)/2,300);break;
</script>

只需要将各个状态的代码加入对应的位置,即可实现运行。各个阶段的代码也可单独运行,所有的代码资源和图片资源已打包上传在该账号的资源上了。

注:本文的代码与图片皆为转发自已有的项目内容,本文内容我个人为项目的经验归纳总结。

网页HTML5--飞机大战小游戏开发--canvas的应用相关推荐

  1. html5飞机大战小游戏开发,html5 飞机大战

    [实例简介]自定义飞机图片数量,子弹图片,速度 [实例截图] [核心代码] var canvas=document.getElementById("myCanvas"); var ...

  2. html+javascript实现的网页版飞机大战小游戏源码

    html+javascript实现的网页版飞机大战小游戏源码 完整代码下载地址: html+javascript实现的网页版飞机大战小游戏源码 index.html <!DOCTYPE html ...

  3. canvas绘制“飞机大战”小游戏,真香

    canvas是ArkUI开发框架里的画布组件,常用于自定义绘制图形.因为其轻量.灵活.高效等优点,被广泛应用于UI界面开发中. 本期,我们将为大家介绍canvas组件的使用. 目录 一.canvas介 ...

  4. Vue 开发一个简略版的飞机大战小游戏

    文章目录 使用 Vue 开发一个简略版的飞机大战小游戏 一.实现思路 二.所需知识点 三.实现步骤 使用 Vue 开发一个简略版的飞机大战小游戏 如题,假设你为了向更多访问你博客的人展示你的技术,你决 ...

  5. 使用小程序制作一个飞机大战小游戏

    此文主要基于微信小程序制作一个飞机大战小游戏,上手即用,操作简单. 一.创建小程序 二.页面实现 三.代码块 一.创建小程序 访问微信公众平台,点击账号注册. 选择小程序,并在表单填写所需的各项信息进 ...

  6. 华为官方解析开源鸿蒙 OpenHarmony 3.1关键特性画布,教你如何完成飞机大战小游戏

    华为技术有限公司的江英杰为大家揭晓了关于开源鸿蒙 OpenHarmony 3.1 Beta 版中的一个关键特性,也就是 ArkUI 开发框架中的 canvas 画布. 据介绍,canvas 是 Ark ...

  7. 飞机大战小游戏(超详细)

    偷学Python之最后的项目二:飞机大战小游戏(超详细) 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志.--苏轼 甜甜先说 这次用Python中的pygame模块来完成一个飞机大战的小游戏:基本思 ...

  8. C语言—飞机大战小游戏

    哈工大经典C语言大作业-飞机大战小游戏,源码如下,已经通过编译获得评分19+ (满分20)当时还是太菜了呜呜呜. 可以给大家参考一下,好像本来是加了音乐的,但是你们可能没有对应的音乐MP3文件,所以如 ...

  9. 基于Java语言在窗体上实现飞机大战小游戏

    全套资料下载地址:https://download.csdn.net/download/sheziqiong/85594271 项目介绍 飞机大战:用 Java 语言在窗体上实现飞机大战小游戏,运行程 ...

最新文章

  1. CodeForces Round #287 Div.2
  2. 马斯克在线“求逮捕”:美国县政府不让特斯拉复工,钢铁侠彻底怒了
  3. 【博客话题】技术人,你肿么了
  4. 代码质量第 5 层 - 只是实现了功能
  5. postman 使用_Postman使用方法
  6. BitCome比特彗星v1.82豪华版(bt下载)
  7. Android LowMemoryKiller ADJ原理
  8. 计算机开机密码设置要求,电脑开机密码怎么设置,开机密码设置很简单!
  9. hdu2028java-Lowest Common Multiple Plus
  10. pr如何处理音效_PR音乐音效处理教程 Premiere Pro CC Essential Sound
  11. 华为血压表WATCH D测量血压的数据可靠吗
  12. 远程桌面桌面无法找到计算机,Windows – 远程桌面客户端找不到远程计算机
  13. 【presto 】presto 新版本升级详情
  14. Leetcode 309. Best Time to Buy and Sell Stock with Cooldown
  15. 网络工程师加入德云社说相声,他还骑摩托车环球旅行!!
  16. mac framework
  17. Hung-Yi Lee homework[7]: Network Compression
  18. 使用七牛的sdk上传报错:incorrect region
  19. 学编程的人那么多,到底编程的出路在哪?
  20. excel表格汇总软件

热门文章

  1. 深度学习笔记(三)—— 反向传播[Back Propagation] 计算图[Computational Graph]
  2. Python(正则表达式)
  3. 雍正王朝上部摘要—摘自电影最top
  4. 【渝粤题库】陕西师范大学200871小学教育学作业(高起专)
  5. 软件自动更新功能的实现
  6. Direct2D 简介
  7. incident用法_incident与_accident区别
  8. 回文日期(日期合法判断)
  9. 客户体验技术领军企业Alvaria, Inc.宣布完成对近期收购的Aspect和Noble Systems的整合
  10. FreeBSD中安装源的方法