目录
1、游戏设计思路
2、飞机射击游戏设计步骤
本篇博文将分享一篇基于HTML的简单的飞机射击游戏,下方是玩家飞机,可按空格键能不断地发射子弹,上方是随机出现的敌方飞机。玩家可以通过键盘的方向键控制自己飞机的移动,当玩家飞机的子弹碰到敌方飞机时,敌方飞机出现爆炸效果。游戏运行效果如下图所示:
1、游戏设计思路
1、游戏素材
游戏程序中用到敌方飞机、我方飞机、子弹、敌机被击中的爆炸图片等,分别使用如下图所示:
2、地图滚动的原理实现
举个简单的例子,大家都坐过火车吧,坐火车的时候都遇到过自己的火车明明是停止的,但是旁边铁轨的火车在向后行驶,会有一种错觉感觉自己的火车是在向前行驶。飞行射击类游戏的地图原理和这个完全一样。玩家在控制飞机在屏幕中飞行的位置,背景图片一直向后滚动从而给玩家一种错觉自己控制的飞机在向前飞行。
如下图所示地图图片在屏幕背后交替滚动,这样就会给玩家产生自己控制的飞机在向前移动的错觉。
地图滚动的相关代码,如下所示:
function updateBg() { /** 更新游戏背景图片实现向下滚动效果**/ mBitposY0 += 5;//第一张地图map_0.png的纵坐标下移5个像素 mBitposY1 += 5;//第二张地图map_1.png的纵坐标下移5个像素 if (mBitposY0 == mScreenHeight) { //超过游戏屏幕的底边 mBitposY0 = -mScreenHeight;//回到屏幕上方 } if (mBitposY1 == mScreenHeight) {//超过游戏屏幕的底边 mBitposY1 = -mScreenHeight; //回到屏幕上方 } }
3、飞机和子弹的实现
游戏中使用到的飞机、子弹均采用对应的类实现。因为子弹的数量会有很多,敌机的数量也会很多,所以每一颗子弹须要用一个对象来记录这当前子弹在屏幕中的X,Y坐标。每一架敌机也是一个对象,也记录着它在屏幕中的X,Y坐标。这样在处理碰撞的时候通过遍历子弹对象与敌机对象就可以计算出碰撞的结果,从而拿到碰撞的敌机对象播放死亡爆炸动画。
游戏过程中每隔3秒添加一架敌机,玩家按空格键发射子弹并初始化其位置坐标在玩家飞机前方。在定时事件中不断更新游戏背景图片位置,下移5个像素,实现向下滚动效果,同时更新每发子弹位置每次上移1个像素,更新敌机位置(每次1个像素),最后检测子弹与敌机的碰撞。
这样在处理碰撞的时候其实就是每一颗子弹的矩形区域与每一架敌机的矩形区域的碰撞。通过遍历子弹对象与敌机对象就可以计算出碰撞的结果,从而得到碰撞的敌机对象并播放死亡爆炸动画。
2、飞机射击游戏设计步骤
1、设计子弹类
创建一个的Bullet类,用于表示子弹,实现子弹坐标更新,绘制子弹动画效果并上移1个像素。子弹是有4帧组成,每10个时间间隔(每个间隔为1000/60=16.67毫秒)换一帧。代码如下所示:
//子弹类 var Bullet = function (image, x, y) { this.image = image; this.x = x; this.y = y; this.width = image.width/4; this.height = image.height ; this.frm = 0; //当前是第几帧 this.dis = 0; //多少时间间隔 };
检测点(x, y)是否在子弹区域内(本游戏没有使用),代码如下所示:
Bullet.prototype.testPoint = function (x, y) { var betweenX = (x >= this.x) && (x <= this.x + this.width); var betweenY = (y >= this.y) && (y <= this.y + this.height); return betweenX && betweenY; }; move改变子弹位置,代码如下所示: Bullet.prototype.move = function (dx, dy) { this.x += dx; this.y += dy; };
draw绘制子弹动画效果并上移1个像素。每10个间隔换一帧,子弹共4帧(图14-7所示)。子弹坐标更新主要修改y坐标(垂直方向)值,每次1个像素。当然也可以修改x坐标(水平方向)值,这里为了简单化没修改x坐标值(水平方向),代码如下所示:
Bullet.prototype.draw = function (ctx) { ctx.save(); ctx.translate(this.x, this.y); ctx.drawImage(this.image, this.frm *this.width, 0 , this.width, this.height, 0, 0, this.width, this.height);//绘制子弹对应this.frm帧 ctx.restore(); this.y--; //上移1个像素 this.dis++; if (this.dis >= 10) {//10个间隔换一帧 this.dis = 0; this.frm++; if (this.frm >= 4) this.frm = 0; }
hitTestObject判断子弹与飞机是否碰撞,代码如下所示:
Bullet.prototype.hitTestObject = function (planobj) { if(isColliding(this.x,this.y,this.width,this.height, planobj.x,planobj.y,planobj.width,planobj.height))//发生碰撞 return true; else return false; }
isColliding全局函数是前面分析的第二种碰撞检测方法,代码如下所示:
function isColliding( ax, ay, aw, ah, bx, by, bw, bh) { if(ay > by + bh || by > ay + ah || ax > bx + bw || bx > ax + aw) return false; else return true; }
2、设计飞机类
在项目中创建一个Plan类,用于表示敌机和己方的飞机,实现飞机坐标更新,绘制功能。功能与子弹类相似。
构造函数中image是飞机图片,(x, y)是飞机位置坐标,而最后一个参数n是本飞机图是几帧动画,例如己方飞机是6帧动画,敌机是2帧动画,效果如下所示:
玩家飞机和敌机
实现代码如下所示:
var Plan = function (image, x, y, n) { this.image = image; this.x = x; this.y = y; this.originX = x; this.originY = y; this.width = image.width / n; //每帧飞机宽度 this.height = image.height; //每帧飞机高度 this.frm = 0; this.dis = 0; this.n = n; }; Plan.prototype.testPoint = function (x, y) { var betweenX = (x >= this.x) && (x <= this.x + this.width); var betweenY = (y >= this.y) && (y <= this.y + this.height); return betweenX && betweenY; }; Plan.prototype.move = function (dx, dy) { this.x += dx; this.y += dy; }; Plan.prototype.Y = function ( ) { return this.y; };
draw (ctx)不断下移地画飞机,同时水平方向也有位移,采用正弦移动,实现代码如下所示:
Plan.prototype.draw = function (ctx) { ctx.save(); ctx.translate(this.x, this.y); ctx.drawImage(this.image, this.frm *this.width, 0 , this.width, this.height, 0, 0, this.width, this.height); ctx.restore(); this.y++; //下移1个像素 this.x = this.originX + 20 * Math.sin(Math.PI / 100 * this.y);//水平方向正弦移动 this.dis++; if (this.dis >= 3) {//3个间隔换图 this.dis = 0; this.frm++; if (this.frm >= this.n) this.frm = 0; } };
draw2 (ctx)原地不动画飞机,因为己方飞机是人工控制移动的,所以需要此函数,实现代码如下所示:
Plan.prototype.draw2 = function (ctx) { ctx.save(); ctx.translate(this.x, this.y); ctx.drawImage(this.image, this.frm *this.width, 0 , this.width, this.height, 0, 0, this.width, this.height); ctx.restore(); this.dis++; if (this.dis >= 3) {//3个间隔换图 this.dis = 0; this.frm++; if (this.frm >= this.n) this.frm = 0; } }; //飞机之间碰撞检测 //如果重叠则说明飞机碰撞。 Plan.prototype.hitTestObject = function (planobj) { if(isColliding(this.x,this.y,this.width,this.height, planobj.x,planobj.y,planobj.width,planobj.height)) //发生碰撞 return true; else return false; }
3、爆炸类
爆炸动画被叫简单,只需原地绘制爆炸的6帧就可以,效果如下所示:
敌机爆炸的6帧图像
实现代码如下所示:
//爆炸动画 var Bomb= function (image, x, y) { this.image = image; this.x = x; this.y = y; this.width = image.width/6; this.height = image.height ; this.frm = 0; this.dis = 0; }; Bomb.prototype.draw2 = function (ctx) { ctx.save(); ctx.translate(this.x, this.y); if (this.frm >= 6) return ;//6帧绘制就结束了 ctx.drawImage(this.image, this.frm *this.width, 0 , this.width, this.height, 0, 0, this.width, this.height); ctx.restore(); this.dis++; if (this.dis >= 10) {//10个间隔换图 this.dis = 0; this.frm++; } };
4、设计主程序
用于实现游戏背景界面,加载游戏相关图片,完成子弹发射、敌机移动,碰撞检测等功能,实现代码如下所示:
var canvas = document.getElementById("myCanvas"); var context = canvas.getContext("2d"); document.addEventListener("keydown", onkeydown); var plans = []; //敌机对象数组 var bullets = []; //子弹对象数组 var bombs = []; //爆炸对象数组 var score=0; var overflag = false; //游戏是否结束,true为结束 var mBitposY0, mBitposY1; /** 屏幕的宽高* */ var mScreenWidth = 320; var mScreenHeight = 480 var myplane;//己方飞机 var image = new Image(); var image2 = new Image(); var image3 = new Image(); var image4 = new Image(); var image5 = new Image(); //以下游戏背景的两张图片 var background0 = new Image(); background0.src = "map_0.png"; var background1 = new Image(); background1.src = "map_1.png";
init()初始化游戏背景的两张图片的初始位置,updateBg()通过这两张背景图片的不断下移和切换实现游戏背景动态移动效果,实现代码如下所示:
function init() { /** 游戏背景* */ /** 第一张图片津贴在屏幕(0,0)点,第二张图片在第一张图片上方* */ mBitposY0 = 0; mBitposY1 = -mScreenHeight; } function updateBg() { /** 更新游戏背景图片实现向下滚动效果**/ mBitposY0 += 5; mBitposY1 += 5; if (mBitposY0 == mScreenHeight) { mBitposY0 = -mScreenHeight; } if (mBitposY1 == mScreenHeight) { mBitposY1 = -mScreenHeight; } } image.src = "plan.png";//自己飞机图片 image.onload = function () { }; image2.src = "bomb.png";//爆炸图片 image2.onload = function () { };
image3.src = "enemy.png";//敌机图片
图片加载成功后,通过定时每3秒产生1架敌机,在另一个定时器中不断更新背景图片位置,画自己方飞机和敌机,并检测是否敌机碰到玩家自己飞机(则游戏结束)或者子弹碰到敌机,最后绘制爆炸对象,实现游戏逻辑。
如果子弹碰撞到敌机,则产生爆炸对象,从敌机数组plans中删除该敌机,从子弹数组bullets中删除碰撞的子弹。如果没击中敌机,再判断子弹是否飞出屏幕上方,飞出屏幕上方则从数组bullets中删除碰撞的子弹。实现代码如下所示:
image3.onload = function () { myplane = new Plan(image, 300 * Math.random(), 400, 6); //6幅图片 init(); //初始化背景地图位置 plan_interval = setInterval(function () { plans.push(new Plan(image3, 300 * Math.random(), 20 * Math.random(), 2)); //2幅图片 }, 3000); //3秒产生1架敌机 setInterval(function () { context.clearRect(0, 0, 320, 480); //画地图 //context.drawImage(background, 0, 0); context.drawImage(background0, 0, mBitposY0); context.drawImage(background1, 0, mBitposY1); updateBg();//更新背景图片位置 //画自己方飞机 if (!overflag)//游戏没有结束 myplane.draw2(context); //原地不动 //画敌人飞机 for (var i = plans.length - 1; i >= 0; i--) { if (plans[i].Y() > 400) //敌机飞到底部则消失 plans.splice(i, 1); //删除敌机 else plans[i].draw(context); } //画子弹 for (var i = bullets.length - 1; i >= 0; i--) { if (bullets[i].Y() < 0) bullets.splice(i, 1); //删除子弹 else bullets[i].draw(context); } //碰撞检测 //判断敌机碰到玩家自己飞机 for (var i = plans.length - 1; i >= 0; i--) { e1 = plans[i]; if (e1 != null && myplane != null && myplane.hitTestObject(e1)) { clearInterval(plan_interval); //清除定时器,不再产生敌机 plans.splice(i, 1); //删除敌机 bombs.push(new Bomb(image2, myplane.x, myplane.y)); //bomb_interval=setInterval(function () { // bomb.draw2(context);//原地不动 //}, 1000 / 60); message_txt.innerHTML = "敌机碰到玩家自己飞机,游戏结束"; overflag = true; } } //判断子弹碰到敌机 for (var j = bullets.length - 1; j >= 0; j--) { var b1 = bullets[j]; for (var i = plans.length - 1; i >= 0; i--) { e1 = plans[i]; if (e1 != null && b1 != null && b1.hitTestObject(e1))//击中敌机 { plans.splice(i, 1); //删除敌机 bullets.splice(i, 1); //删除此颗子弹 bombs.push(new Bomb(image2, b1.x, b1.y - 36)); message_txt.innerHTML = "敌机被击中,加20分"; score += 20; score_txt.innerHTML = "分数:" + score + "分"; } } } //画爆炸 for (var i = bombs.length - 1; i >= 0; i--) { if (bombs[i].frm >= 6) bombs.splice(i, 1); //删除爆炸 else bombs[i].draw2(context); } }, 1000 / 60); }; image4.src = "bullet.png";//子弹图片 image4.onload = function () { };
用户按键控制飞机上下左右移动,及空格发射子弹。onkeydown(e)响应用户的按键操作,修改玩家自己飞机的坐标,如下所示:
function onkeydown(e) { if (e.keyCode==32) {//空格 //发射子弹 bullets.push(new Bullet(image4, myplane.x, myplane.y-36));// }else if (e.keyCode==37) {//向左 myplane.move(-10,0); }else if (e.keyCode==39) {//向右 myplane.move(10,0); }else if (e.keyCode==38) {//向上 myplane.move(0,-10); }else if (e.keyCode==40) {//向下 myplane.move(0,10); } }
5、游戏页面
实现代码如下所示:
<!DOCTYPE html> <html> <head> <title>飞机大战2017</title> <meta charset="utf-8"> </head> <body> <canvas id="myCanvas" width="320" height="480" style="border:solid">
你的浏览器不支持canvas画布元素,请更新浏览器获得演示效果。
</canvas> <div id="message_txt" style="display:block;">飞机大战</div> <div id="score_txt" style="display:block;">分数:0分</div> </body> </html>
项目整理来源于:清华计算机学堂