您好疲憋,本篇文章主要描述如何用面向?qū)ο缶幊趟枷敕聦?xiě)微信飛機(jī)大戰(zhàn)。
采用JS腳本來(lái)控制HTML5新標(biāo)簽canvas畫(huà)布,因?yàn)橹徊僮饕粋€(gè)DOM茎用,畫(huà)面會(huì)更加流暢,很多基于網(wǎng)頁(yè)開(kāi)發(fā)的小游戲都是用canvas畫(huà)布實(shí)現(xiàn)的睬罗。
0 準(zhǔn)備工作
首先轨功,我們要準(zhǔn)備好各種圖片,如飛機(jī)的雪碧(精靈)圖容达、子彈圖古涧、背景圖。
然后花盐,對(duì)整個(gè)游戲流程進(jìn)行構(gòu)思:
①游戲開(kāi)始前的圖片預(yù)加載
②讓背景圖片動(dòng)起來(lái)
③繪制英雄機(jī)羡滑,英雄機(jī)跟隨鼠標(biāo)移動(dòng)
④英雄機(jī)能發(fā)射子彈
⑤繪制3種敵機(jī),賦予它們不同的血量與下落速度
⑥子彈與敵機(jī)的碰撞檢測(cè)
⑦英雄機(jī)與敵機(jī)的碰撞檢測(cè)
⑧顯示分?jǐn)?shù)算芯,與結(jié)束游戲
1 HTML+CSS
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>微信飛機(jī)大戰(zhàn)</title>
</head>
<body>
//
<canvas id="canvas" width="320" height="528" style="border: 2px solid gray"></canvas>
//引入腳本文件
<script src=""airplane></script>
</body>
</html>
2 JS
2.1 所有圖片預(yù)加載
//獲取畫(huà)布與畫(huà)布上下文
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
//分?jǐn)?shù)變量 游戲是否開(kāi)始變量 英雄機(jī)對(duì)象
var score = 0;
var gameStart = false;
var theHero = null;
// 創(chuàng)建對(duì)象來(lái)接收對(duì)應(yīng)的圖片對(duì)象
var bgImg = '';
var heroImg = '';
var enemy1Img = '';
var enemy2Img = '';
var enemy3Img = '';
var bullet1Img = '';
//繪制進(jìn)度條構(gòu)造函數(shù)
function Loading(){
this.width = 220;
this.height = 12;
this.x = 50;
this.y = 258;
}
//創(chuàng)建進(jìn)度條原型
Loading.prototype = {
//畫(huà)進(jìn)度條的外框
drawStroke : function(){
ctx.beginPath();
ctx.strokeRect(this.x,this.y,this.width,this.height);
ctx.stroke();
ctx.closePath();
},
//畫(huà)已加載的填充圖
drawFill : function(){
ctx.beginPath();
ctx.fillStyle = 'orangered';
ctx.fillRect(this.x,this.y,this.width,this.height);
ctx.fill();
ctx.closePath();
}
}
// 預(yù)加載函數(shù)
function load(){
var loadS = new Loading();
var loadSW = loadS.width;
loadS.drawStroke();
var loadF = new Loading();
loadF.width = 0;
loadF.drawFill();
//用一個(gè)數(shù)組把圖片地址保存起來(lái)
var arr = ['img/bg.png','img/hero.png',
'img/enemy1.png','img/enemy2.png',
'img/enemy3.png','img/bullet1.png'
];
var index = 0;
//利用循環(huán)柒昏,創(chuàng)建圖片對(duì)象
for(var i = 0;i<arr.length;++i){
var img = new Image();
img.src = arr[i];
//利用正則表達(dá)式判斷圖片對(duì)象是否為背景圖,如果是就用一個(gè)變量保存起來(lái)也祠,其他圖片也是類似
if(/bg/.test(arr[i])){
bgImg = img;
}
else if(/hero/.test(arr[i])){
heroImg = img;
}
else if(/enemy1/.test(arr[i])){
enemy1Img = img;
}
else if(/enemy2/.test(arr[i])){
enemy2Img = img;
}
else if(/enemy3/.test(arr[i])){
enemy3Img = img;
}
else if(/bullet1/.test(arr[i])){
bullet1Img = img;
}
//當(dāng)本次循環(huán)的圖片加載完畢昙楚,運(yùn)行該函數(shù)
img.onload = function(){
//加載增量
index++;
//進(jìn)度條的長(zhǎng)度隨著圖片加載個(gè)數(shù)的增加而變長(zhǎng)
loadF.width = (index/arr.length)*loadSW;
ctx.clearRect(0,0,canvas.width,canvas.height);
loadF.drawFill();
if(index >=arr.length){
//當(dāng)圖片全部加載完畢,繪畫(huà)初始界面
theHero = new Hero(heroImg,6);
ctx.fillStyle = 'black';
ctx.font = '30px Consoles';
ctx.fillText('飛機(jī)大戰(zhàn)',canvas.width/3.5,canvas.height/3);
ctx.fillStyle = 'orange';
ctx.fillRect(canvas.width/3.5,canvas.height-200,130,50);
ctx.fillStyle = 'black';
ctx.fillText('開(kāi)始游戲',canvas.width/3.3,canvas.height-163);
}
}
}
}
load();
2.2 為開(kāi)始按鈕添加點(diǎn)擊事件
因?yàn)樵诋?huà)布內(nèi)诈嘿,所以只能根據(jù)位置判斷是否點(diǎn)擊到按鈕
canvas.onmousedown = function(e){
var e = e||window.event;
var x = e.clientX - canvas.offsetLeft;
var y = e.clientY - canvas.offsetTop;
// 開(kāi)始游戲的框大小內(nèi) canvas.width/3.5,canvas.height-200,130,50
if(x>=canvas.width/3.5&&(x<=canvas.width/3.5+130)&&y>=canvas.height-200&&y<canvas.height-200+50){
gameStart = true;
//動(dòng)畫(huà)主函數(shù)
move();
//點(diǎn)擊完就把畫(huà)布點(diǎn)擊事件置為空
canvas.onmousedown = null;
}
}
2.3 背景移動(dòng)函數(shù)
利用ctx.drawImage()繪制兩張背景圖堪旧,同時(shí)向下移動(dòng)削葱,當(dāng)?shù)谝粡垐D片移動(dòng)到底部后就改變圖片位置。兩張圖一直輪播淳梦。
// 設(shè)置背景移動(dòng)量與背景位置變量
var bgChange = 1;
var bgPos = 0;
// 背景移動(dòng)函數(shù)
function bgMove(){
bgPos += bgChange;
if(bgPos==canvas.height){
bgPos = 0;
ctx.drawImage(bgImg,0,0);
}
ctx.drawImage(bgImg,0,-canvas.height+bgPos);
ctx.drawImage(bgImg,0,bgPos);
}
2.4 各種對(duì)象的構(gòu)造函數(shù)
2.4.1 英雄機(jī)構(gòu)造函數(shù)
//傳入對(duì)象為英雄機(jī)圖片
function Hero(obj,health){
this.obj = obj;
this.width = obj.width/health;
this.height = obj.height;
//英雄機(jī)血量
this.health = health;
this.boom = 0;
//英雄機(jī)初始位置
this.x = canvas.width/2 -this.width/2;
this.y = canvas.height - this.height;
}
Hero.prototype.draw = function(){
ctx.drawImage(this.obj,this.width*this.boom,0,this.width,this.height,this.x,this.y,this.width,this.height);
}
2.4.2 子彈構(gòu)造函數(shù)
//創(chuàng)建一個(gè)數(shù)組存放子彈對(duì)象
var arrBullet = [];
function Bullet(x,y,speedY){
//子彈初始位置
this.x = theHero.x+31;
this.y = theHero.y-15;
//子彈向上運(yùn)動(dòng)速度
this.speedY = speedY || -20;
this.width = bullet1Img.width;
this.height = bullet1Img.height
}
Bullet.prototype = {
draw : function(){
ctx.drawImage(bullet1Img,this.x,this.y);
},
move : function(){
this.draw();
this.y += this.speedY;
},
//當(dāng)子彈到達(dá)頂部析砸,返回布爾值true
clear : function(){
if(this.y<=0){
return true;
}
else{
return false;
}
}
}
2.4.3 敵機(jī)構(gòu)造函數(shù)
//創(chuàng)建一個(gè)數(shù)組用來(lái)存放敵機(jī)對(duì)象
var arrEn1 = [];
//敵機(jī)構(gòu)造函數(shù)
function Enemy(obj,speedY,health,type){
//傳入的對(duì)象為 3種不同的敵機(jī)圖片
this.obj = obj;
this.width = obj.width/health;
this.height = obj.height;
//敵機(jī)出現(xiàn)位置設(shè)定為隨機(jī)
this.x = rnd(canvas.width-this.width,0);
this.y = rnd(-canvas.height,-obj.height);
this.speedY = speedY || 1;
//敵機(jī)血量
this.health = health;
//敵機(jī)是否被打中
this.boom = 0;
//敵機(jī)類型
this.type = type;
}
Enemy.prototype = {
//繪畫(huà)敵機(jī)
draw : function(){
ctx.drawImage(this.obj,this.width*this.boom,0,this.width,this.height,this.x,this.y,this.width,this.height);
},
//敵機(jī)移動(dòng)
move : function(){
this.draw();
this.y += this.speedY;
},
//如果敵機(jī)超越畫(huà)布底部 把清除敵機(jī)的布爾值置為true
clear : function(){
if(this.y>=canvas.height){
return true;
}
else{
return false;
}
}
}
//隨機(jī)函數(shù)
function rnd(max,min){
return Math.random()*(max-min+1)+min;
}
2.5 英雄機(jī)跟隨鼠標(biāo)
canvas.onmousemove = function(e){
var e = e||window.event;
var ex = e.clientX - canvas.offsetLeft - 33;
var ey = e.clientY - canvas.offsetTop - 41;
theHero.x = ex;
theHero.y = ey;
}
3 主動(dòng)畫(huà)函數(shù)
//創(chuàng)造一個(gè)變量用來(lái)決定敵機(jī)出現(xiàn)概率
var num = 0;
function move(){
num++;
if(num==1000){
num = 0;
}
//每次開(kāi)始繪畫(huà)時(shí),先清除畫(huà)布
ctx.clearRect(0,0,canvas.width,canvas.height);
//調(diào)用背景移動(dòng)函數(shù)
bgMove();
//繪畫(huà)英雄機(jī)
theHero.draw();
// 創(chuàng)造子彈對(duì)象爆袍,并把該對(duì)象插入到子彈數(shù)組
if(num%5==0){
var bullet = new Bullet();
arrBullet.push(bullet);
}
// 創(chuàng)造小敵機(jī)對(duì)象(出現(xiàn)幾率最大)首繁,并把該對(duì)象插入到敵機(jī)數(shù)組
//(出現(xiàn)幾率最大,速度最快2.5陨囊,血量最低5)
if(num%45==0){
var en1 = new Enemy(enemy1Img,2.5,5,'en1');
arrEn1.push(en1);
}
// 創(chuàng)造中敵機(jī)對(duì)象弦疮,并把該對(duì)象插入到敵機(jī)數(shù)組
//(出現(xiàn)幾率最小,速度一般2蜘醋,血量一般7)
if(num%160==0){
var en3 = new Enemy(enemy3Img,2,7,'en2');
arrEn1.push(en3);
}
// 創(chuàng)造大敵機(jī)對(duì)象胁塞,并把該對(duì)象插入到敵機(jī)數(shù)組
//(出現(xiàn)幾率最小,速度最慢1.5压语,血量最高10)
if(num%360==0){
var en2 = new Enemy(enemy2Img,1.5,10,'en3');
arrEn1.push(en2);
}
// 遍歷子彈數(shù)組畫(huà)子彈
for(var i =0;i<arrBullet.length;++i){
//如果子彈到達(dá)底部啸罢,從數(shù)組中清除該對(duì)象
//清除后減少數(shù)組長(zhǎng)度并跳出本次循環(huán)
if(arrBullet[i].clear()){
arrBullet.splice(i,1);
i--;
continue;
}
//如沒(méi)有被清除則繪畫(huà)該子彈對(duì)象
arrBullet[i].move();
}
// 遍歷敵機(jī)數(shù)組畫(huà)敵機(jī)
for(var j = 0;j<arrEn1.length;++j){
arrEn1[j].y += arrEn1[j].speedY;
//如果玩家得分超過(guò)15000就加快敵機(jī)速度
if(score>=15000){
arrEn1[j].y += arrEn1[j].speedY*1.3;
}
//如果敵機(jī)到達(dá)畫(huà)布底部,從數(shù)組中清除該對(duì)象
if(arrEn1[j].clear()){
arrEn1.splice(j,1);
j--;
continue;
}
//若沒(méi)有被清除則繪畫(huà)該敵機(jī)對(duì)象
arrEn1[j].draw();
}
// 英雄機(jī)與敵機(jī)碰撞檢測(cè) (矩形碰撞)
for(var i = 0;i<arrEn1.length;++i){
//獲得英雄機(jī)上下左右位置
var heroL = theHero.x;
var heroT = theHero.y;
var heroR = theHero.x + theHero.width;
var heroB = theHero.y + theHero.health;
//獲取當(dāng)前循環(huán)中敵機(jī)上下左右位置
var enL = arrEn1[i].x;
var enT = arrEn1[i].y;
var enR = arrEn1[i].x + arrEn1[i].width;
var enB = arrEn1[i].y + arrEn1[i].height;
//矩形碰撞條件為:不碰撞的4種情況取反
if(!(enR<heroL||enB<heroT||enL>heroR||enT>heroB)){
//判斷敵機(jī)是否爆炸完成胎食,是則清除該敵機(jī)對(duì)象
if(arrEn1[i].boom==arrEn1[i].health-1){
arrEn1.splice(i,1);
i--;
continue;
}
//碰撞到時(shí)扰才,英雄機(jī)與敵機(jī)爆炸增量都自增1
else{
theHero.boom++;
arrEn1[i].boom++;
}
}
//如果英雄機(jī)爆炸量與血量相同,則判斷游戲結(jié)束
if(theHero.boom>theHero.health-1){
theHero.boom = theHero.health;
theHero.draw();
//把游戲進(jìn)行布爾值置為false
gameStart = false;
//繪畫(huà)所得分?jǐn)?shù)
ctx.fillStyle = 'orangered';
ctx.font = '20px Consoles';
ctx.fillText('游戲結(jié)束厕怜,您獲得的分?jǐn)?shù):'+score,10,canvas.height/2);
break;
}
}
//繪畫(huà)英雄機(jī)血量條
ctx.fillRect(10,canvas.height-20,350*((theHero.health-1-theHero.boom)/theHero.health),10);
// 判斷子彈擊中敵機(jī)
for(var i = 0;i<arrBullet.length;++i){
for(var j=0;j<arrEn1.length;++j){
//兩個(gè)循環(huán)分別遍歷子彈數(shù)組與敵機(jī)數(shù)組
//獲得每個(gè)子彈對(duì)象的位置與每個(gè)敵機(jī)的位置
var btL = arrBullet[i].x;
var btR = arrBullet[i].x + arrBullet[i].width;
var btT = arrBullet[i].y;
var btB = arrBullet[i].y + arrBullet[i].height;
var enL = arrEn1[j].x;
var enR = arrEn1[j].x + arrEn1[j].width;
var enT = arrEn1[j].y;
var enB = arrEn1[j].y + arrEn1[j].height;
//對(duì)它們逐一進(jìn)行判斷是否碰撞
if(!(btL>enR||btR<enL||btT>enB||btB<enT)){
//碰撞到就在子彈數(shù)組中刪除該子彈對(duì)象
arrBullet.splice(i,1);
i--;
//打中一下衩匣,爆炸增量加1
arrEn1[j].boom++;
//爆炸增量等于血量時(shí),清除該敵機(jī)對(duì)象
if(arrEn1[j].boom==arrEn1[j].health-1){
//擊中對(duì)應(yīng)敵機(jī)加對(duì)應(yīng)分?jǐn)?shù)
if(arrEn1[j].type=='en1'){
score += 300;
}
else if(arrEn1[j].type=='en2'){
score += 500;
}
else if(arrEn1[j].type=='en3'){
score += 1000;
}
//刪除對(duì)應(yīng)敵機(jī)對(duì)象
arrEn1.splice(j,1);
j--;
break;
}
break;
}
}
}
//在左上角繪畫(huà)分?jǐn)?shù)
ctx.fillStyle = 'black';
ctx.font = '20px Consoles';
ctx.fillText('分?jǐn)?shù):'+score,10,30);
ctx.fillStyle = 'orange';
//如果游戲進(jìn)行布爾值為true酣倾,
//則利用window.requestAnimationFrame重復(fù)調(diào)用move主動(dòng)畫(huà)函數(shù)
//進(jìn)行不斷繪畫(huà)
if(gameStart == true){
window.requestAnimationFrame(move);
}
}
4 最后效果圖
5 總結(jié)
原來(lái)做一個(gè)游戲需要做好很多工作舵揭,這是我以前作為一個(gè)玩家所無(wú)法體會(huì)到的,在此感謝給予過(guò)我游戲樂(lè)趣的前輩們躁锡。