坦克大戰小遊戲關鍵技術總結
阿新 • • 發佈:2019-02-09
這兩天參照韓順平的坦克大戰遊戲並在其基礎上初步完善成一個小的頁面遊戲,最終js程式碼大約570行,html程式碼200多行,具體可參見https://github.com/Mrkaiyang/Art/tree/gh-pages/tank。主要實現了以下功能:
- 英雄坦克控制移動、射擊,敵人坦克自動移動、射擊;
- 添加了老巢與保護牆,使得坦克無法穿過牆,但是可以擊破牆;
現對其主要程式設計思想做下總結:1、使用html5的<canvas>元素繪製坦克,
主要用到了以下技術:
1、JavaScript面向物件的程式設計(包括三類物件:坦克、子彈、牆);
//利用建構函式構建坦克類,屬性包括:坦克的橫縱座標、坦克的方向、坦克移動速度、坦克的型別(英雄坦克/敵人坦克)、坦克生存狀態判斷屬性;方法實現了坦克上下左右移動
function Tank(x,y,direct,speed,type) { this.x=x; this.y=y; this.speed=speed; this.direct=direct; this.isLive=true; this.type=type; this.moveUp=function () { this.direct=0; this.y-=this.speed; } this.moveDown=function () { this.direct=2; this.y+=this.speed; } this.moveRight=function () { this.direct=1; this.x+=this.speed; } this.moveLeft=function () { this.direct=3; this.x-=this.speed; } }
//構造子彈類,屬性包括:子彈橫縱座標,子彈的方向,子彈的移動速度,子彈的型別(英雄子彈/敵人子彈),子彈存在判斷屬性,所屬坦克等;方法實現了子彈的移動的條件及位置移動
function Bullet(x,y,direct,speed,type,tank) { this.x=x; this.y=y; this//利用建構函式構造牆體類,屬性包括:牆的橫縱座標、牆的存在判斷屬性;.isLive=true; this.timer=timer; this.direct=direct; this.speed=speed; this.type=type; this.tank=tank; this.run=function() { if(this.x<0||this.x>500||this.y<10||this.y>600||this.isLive==false){ clearInterval(this.timer); this.isLive=false; if(this.type=="enemy"){ this.tank.bulletIsLive=false; } }else { switch (this.direct) { case 0: this.y -= this.speed; break; case 1: this.x += this.speed; break; case 2: this.y += this.speed; break; case 3: this.x -= this.speed; break; } } } }
function Wall(x,y){ this.x=x; this.y=y; this.isLive=true; }
//利用物件冒充繼承方法構造英雄坦克類,英雄坦克自帶射擊方法
var heroBullets=new Array(); function Hero(x,y,direct,speed,type) { //通過物件冒充達到繼承的的效果 this.tank=Tank; this.tank(x,y,direct,speed,type); this.shotEnermy=function () { switch (this.direct){ case 0: heroBullet=new Bullet(this.x+9,this.y,this.direct,10);//全域性變數 break; case 1: heroBullet=new Bullet(this.x+30,this.y+9,this.direct,10);//全域性變數 break; case 2: heroBullet=new Bullet(this.x+9,this.y+30,this.direct,10);//全域性變數 break; case 3: heroBullet=new Bullet(this.x,this.y+9,this.direct,10);//全域性變數 break; } heroBullets.push(heroBullet); heroBullet.timer=timer//面向物件引用傳遞; var timer=window.setInterval("heroBullets["+(heroBullets.length-1)+"].run()",50); } }
//利用物件冒充繼承方法構造敵人坦克類
function EnemyTank(x,y,direct,speed,type) { this.tank=Tank;//繼承實現 this.isLive=true; this.bulletIsLive=true; //this.enermy(x,y,direct,speed,type); this.count=0; this.bulletIsLive=true; this.tank(x,y,direct,speed,type); this.run=function () { var flag = null; switch (this.direct) { case 0: for (var i = 0; i < wall.length; i++) {//實現遇到牆體換向 if ((this.y > 0) && ((this.y - this.speed) >= wall[i].y) &&((this.y - this.speed) < wall[i] + 40) && (this.x >= wall[i].x) &&( this.x < wall[i].x + 40)&&(wall[i].isLive==true)) { flag = 1; } } if (this.y > 0 && flag != 1) { this.y -= this.speed; } else { this.direct = Math.round(Math.random() * 3); } break; case 1: for (var i = 0; i < wall.length; i++) { if ((this.x+20 <500) && (this.y+30 > wall[i].y) && (this.y < (wall[i].y + 40)) && ((this.x+30 + this.speed) >= wall[i].x) && ((this.x + this.speed) < (wall[i].x + 40))&&(wall[i].isLive==true)){ flag = 1; } } if (this.x + 30 < 500 && flag != 1) { this.x += this.speed; } else { this.direct = Math.round(Math.random() * 3); } break; case 2: for(var i=0;i<wall.length;i++){ if ((this.y+30<600) && ((this.y+30 + this.speed)>= wall[i].y) &&(this.y+30 + this.speed) < (wall[i].y + 40) && (this.x+20 >= wall[i].x) &&( this.x < wall[i].x + 40)&&(wall[i].isLive==true)){ flag = 1; } } if (this.y+30<600&& flag != 1) { this.y += this.speed; } else { this.direct = Math.round(Math.random() *3); } break; case 3: for (var i = 0; i < wall.length; i++) { if ((this.x-20 > 0) && (this.y+30 > wall[i].y) && (this.y < (wall[i].y + 40)) && ((this.x - this.speed) >= wall[i].x) && ((this.x - this.speed) < (wall[i].x + 40))&&(wall[i].isLive==true)){ flag = 1; } } if (this.x > 0 && flag != 1) { this.x -= this.speed; } else { this.direct = Math.round(Math.random() * 3); } break; } if (this.count > 30) { this.direct = Math.round(Math.random() * 3);//隨機生成 0,1,2,3 this.count = 0; } this.count++; } }
2、繪圖技術;利用html5<canvas>元素繪製作戰區域,坦克及子彈
<canvas id="tankMap1" width="500px" height="600px" style="background-color:black" ></canvas>
<script type="text/javascript"> var drawing=document.getElementById("tankMap1"); var cxt=drawing.getContext('2d');
var hero=new Hero(150,420,0,5,"hero");
//繪製坦克 function drawTank(tank) { if (tank.isLive==true){ switch (tank.direct){ case 0: case 2: if(tank.type=="hero"){ cxt.fillStyle="#DED284"; }else if(tank.type=="enemy"){ cxt.fillStyle="yellow"; } cxt.fillRect(tank.x,tank.y,5,30);cxt.fillRect(tank.x+15,tank.y,5,30); cxt.fillRect(tank.x+6,tank.y+5,8,20); if(tank.type=="hero"){ cxt.fillStyle="#FFD972"; }else if(tank.type=="enemy"){ cxt.fillStyle="red"; } cxt.arc(tank.x+10,tank.y+15,4,0,2*Math.PI,true); cxt.fill(); //������Ͳ //cxt.strokeStyle="#FFD972"; if(tank.type=="hero"){ cxt.strokeStyle="#FFD972"; }else if(tank.type=="enemy"){ cxt.strokeStyle="red"; } cxt.lineWidth=2; cxt.beginPath(); cxt.moveTo(tank.x+10,tank.y+15); if(tank.direct==0){ cxt.lineTo(tank.x+10,tank.y); }else if(tank.direct==2) { cxt.lineTo(tank.x+10,tank.y+30) } cxt.closePath(); cxt.stroke(); break; case 1: case 3: if(tank.type=="hero"){ cxt.fillStyle="#DED284"; }else if(tank.type=="enemy"){ cxt.fillStyle="yellow"; } cxt.fillRect(tank.x,tank.y,30,5);cxt.fillRect(tank.x,tank.y+15,30,5); cxt.fillRect(tank.x+5,tank.y+6,20,8); if(tank.type=="hero"){ cxt.fillStyle="#FFD972"; }else if(tank.type=="enemy"){ cxt.fillStyle="red"; } cxt.arc(tank.x+15,tank.y+10,4,0,2*Math.PI,true); cxt.fill(); //������Ͳ /* cxt.strokeStyle="#FFD972"; cxt.lineWidth=2;*/ if(tank.type=="hero"){ cxt.strokeStyle="#FFD972"; }else if(tank.type=="enemy"){ cxt.strokeStyle="red"; } cxt.lineWidth=2; cxt.beginPath(); cxt.moveTo(tank.x+15,tank.y+10); if(tank.direct==1){ cxt.lineTo(tank.x+30,tank.y+10); }else if(tank.direct==3) { cxt.lineTo(tank.x,tank.y+10) } cxt.closePath(); cxt.stroke(); break; } } }</script>
//繪製英雄子彈
function drawHeroBullet(){ for(var i=0;i<heroBullets.length;i++){ var heroBullet=heroBullets[i]; if(heroBullet!=null&&heroBullet.isLive){ cxt2.fillStyle="yellow"; cxt2.fillRect(heroBullet.x,heroBullet.y,2,2); } } }//繪製敵人子彈
function drawEnemyBullet(tank,i,bullet){var etBullet = bullet;if (etBullet.isLive == true&&tank.bulletIsLive==true&&tank.isLive==true) { cxt.fillStyle = "#00FEFE"; cxt.fillRect(etBullet.x, etBullet.y, 2, 2); } }//繪製牆體
function drawWall() { var wall=new Array(wall1,wall2,wall3,wall4,wall5,wall6,wall7,wall8,wall9,wall10,wall11,wall12,wall13,wall14); for (var i = 0; i < wall.length; i++) { if(wall[i].isLive==true){ var img = new Image() img.src = "wall_1.png" cxt.drawImage(img, wall[i].x, wall[i].y); } } }
3、介面程式設計;
4、多個獨立的定時器系統工作;
//啟動這個敵人的坦克 window.setInterval("enemyTanks["+i+"].run()",50);
//不停地重新整理畫圖,實現坦克移動;
var timer1=setInterval("flashTanMap()",100);
//不停地重新整理畫圖,實現英雄子彈移動;
var timer=setInterval("flashBullet()",100);5、網頁遊戲的程式設計思想;
難點所在:
1、如何實現坦克爆炸後產生爆炸動畫效果?
//定義一個炸彈類 function Bomb(x,y){ this.x=x; this.y=y; this.isLive=true; //炸彈是否活的,預設true; //炸彈有一個生命值 this.blood=9; //減生命值 this.bloodDown=function(){ if(this.blood>0){ this.blood--; }else{ //說明炸彈死亡 this.isLive=false; } } }
//編寫一個函式,專門用於判斷我的子彈,是否擊中了某個敵人坦克 function isHitEnemyTank(){ //取出每顆子彈 for(var i=0;i<heroBullets.length;i++){ //取出一顆子彈 var heroBullet=heroBullets[i]; /* alert(heroBullet.islive);*/ if(heroBullet.isLive===true){ //子彈是活的,才去判斷 //讓這顆子彈去和遍歷每個牆判斷 for(var i=0;i<wall.length;i++){ var walls=wall[i]; if(walls.isLive==true){ if(heroBullet.x>=wall[i].x&&heroBullet.x<=wall[i].x+40 &&heroBullet.y>=wall[i].y&&heroBullet.y<=wall[i].y+40){ wall[i].isLive=false; heroBullet.isLive=false; } } } //讓這顆子彈去和遍歷每個敵人坦克判斷 for(var j=0;j<enemyTanks.length;j++){ var enemyTank=enemyTanks[j]; if(enemyTank.isLive===true){ //子彈擊中敵人坦克的條件是什麼? 很多思路 , 韓老師的思想是 //(看看這顆子彈,是否進入坦克所在矩形) //根據當時敵人坦克的方向來決定 switch