网页HTML5--飞机大战小游戏开发--canvas的应用
一,概述
此小项目,是用来练习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的应用相关推荐
- html5飞机大战小游戏开发,html5 飞机大战
[实例简介]自定义飞机图片数量,子弹图片,速度 [实例截图] [核心代码] var canvas=document.getElementById("myCanvas"); var ...
- html+javascript实现的网页版飞机大战小游戏源码
html+javascript实现的网页版飞机大战小游戏源码 完整代码下载地址: html+javascript实现的网页版飞机大战小游戏源码 index.html <!DOCTYPE html ...
- canvas绘制“飞机大战”小游戏,真香
canvas是ArkUI开发框架里的画布组件,常用于自定义绘制图形.因为其轻量.灵活.高效等优点,被广泛应用于UI界面开发中. 本期,我们将为大家介绍canvas组件的使用. 目录 一.canvas介 ...
- Vue 开发一个简略版的飞机大战小游戏
文章目录 使用 Vue 开发一个简略版的飞机大战小游戏 一.实现思路 二.所需知识点 三.实现步骤 使用 Vue 开发一个简略版的飞机大战小游戏 如题,假设你为了向更多访问你博客的人展示你的技术,你决 ...
- 使用小程序制作一个飞机大战小游戏
此文主要基于微信小程序制作一个飞机大战小游戏,上手即用,操作简单. 一.创建小程序 二.页面实现 三.代码块 一.创建小程序 访问微信公众平台,点击账号注册. 选择小程序,并在表单填写所需的各项信息进 ...
- 华为官方解析开源鸿蒙 OpenHarmony 3.1关键特性画布,教你如何完成飞机大战小游戏
华为技术有限公司的江英杰为大家揭晓了关于开源鸿蒙 OpenHarmony 3.1 Beta 版中的一个关键特性,也就是 ArkUI 开发框架中的 canvas 画布. 据介绍,canvas 是 Ark ...
- 飞机大战小游戏(超详细)
偷学Python之最后的项目二:飞机大战小游戏(超详细) 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志.--苏轼 甜甜先说 这次用Python中的pygame模块来完成一个飞机大战的小游戏:基本思 ...
- C语言—飞机大战小游戏
哈工大经典C语言大作业-飞机大战小游戏,源码如下,已经通过编译获得评分19+ (满分20)当时还是太菜了呜呜呜. 可以给大家参考一下,好像本来是加了音乐的,但是你们可能没有对应的音乐MP3文件,所以如 ...
- 基于Java语言在窗体上实现飞机大战小游戏
全套资料下载地址:https://download.csdn.net/download/sheziqiong/85594271 项目介绍 飞机大战:用 Java 语言在窗体上实现飞机大战小游戏,运行程 ...
最新文章
- CodeForces Round #287 Div.2
- 马斯克在线“求逮捕”:美国县政府不让特斯拉复工,钢铁侠彻底怒了
- 【博客话题】技术人,你肿么了
- 代码质量第 5 层 - 只是实现了功能
- postman 使用_Postman使用方法
- BitCome比特彗星v1.82豪华版(bt下载)
- Android LowMemoryKiller ADJ原理
- 计算机开机密码设置要求,电脑开机密码怎么设置,开机密码设置很简单!
- hdu2028java-Lowest Common Multiple Plus
- pr如何处理音效_PR音乐音效处理教程 Premiere Pro CC Essential Sound
- 华为血压表WATCH D测量血压的数据可靠吗
- 远程桌面桌面无法找到计算机,Windows – 远程桌面客户端找不到远程计算机
- 【presto 】presto 新版本升级详情
- Leetcode 309. Best Time to Buy and Sell Stock with Cooldown
- 网络工程师加入德云社说相声,他还骑摩托车环球旅行!!
- mac framework
- Hung-Yi Lee homework[7]: Network Compression
- 使用七牛的sdk上传报错:incorrect region
- 学编程的人那么多,到底编程的出路在哪?
- excel表格汇总软件
热门文章
- 深度学习笔记(三)—— 反向传播[Back Propagation] 计算图[Computational Graph]
- Python(正则表达式)
- 雍正王朝上部摘要—摘自电影最top
- 【渝粤题库】陕西师范大学200871小学教育学作业(高起专)
- 软件自动更新功能的实现
- Direct2D 简介
- incident用法_incident与_accident区别
- 回文日期(日期合法判断)
- 客户体验技术领军企业Alvaria, Inc.宣布完成对近期收购的Aspect和Noble Systems的整合
- FreeBSD中安装源的方法