主頁 > 前端設計 > 基于JavaScript Canvas的植物大戰僵尸,周末爆肝之作,請點個贊再走!

基于JavaScript Canvas的植物大戰僵尸,周末爆肝之作,請點個贊再走!

2021-04-21 11:36:04 前端設計

引言:

前兩天看到小朋友在玩植物大戰僵尸,想起來多年以前自己也經常玩這個游戲,是比較經典的一款休閑游戲,然后就心血來潮就周末寫了一個,花了不少時間去找素材和撰寫代碼,感覺上基本的功能是做好了(要上班,沒那么多時間搞),寫出來大家看看,確實有點爆肝!

效果圖:

實作思路

  1. 用兩張畫布來實作,第一個畫布繪制不用更新的東西,比如背景圖、按鈕、積分圖,卡牌圖等;
  2. 第二個畫布,繪制經常更新的東西,比如僵尸的走動、僵尸吃植物、僵尸死亡、植物的搖擺、豌豆苗發射豌豆、子彈的運動、陽光的產生、陽光的收集等等;
  3. 影片的實作,通過圖片的不停切換來實作的,開啟一個總定時任務100毫秒重新繪制畫布2,當然其他的每個影片都會重新開啟定時任務(我稱他們為子任務),它們不負責繪制,只負責改變對應的引數,繪制都是由總任務來完成的, 比如僵尸走動影片:開啟子任務100毫秒執行一次圖片切換,切換到最后一張的時候,回傳到第一張,如果要走動的話同時改變圖片的位置就好,子任務修改完成后,總任務自然會繪制出來;
  4. 卡牌的實作,目前就寫了2張卡牌(向日葵、豌豆苗),給卡牌繪制了相同大小的方形來控制滑鼠點擊事件,當點擊卡牌的時候,會創建對應的植物并且跟隨滑鼠移動,移動滑鼠到合適的位置后點擊(田 里面對應的方塊),會在對應的位置種植;
  5. 田位置的控制,以方形來劃分,每一塊可以種植物的區域都用一個小方塊來控制,植物就種在對應的方塊內,當選擇一個卡牌后,滑鼠移動到田里面就會標示出來一個方形的區域,標示植物種植在這塊區域里面,
  6. 豌豆苗被種植后,會定時的發射子彈,當子彈的位置和僵尸的位置交匯的時候,就判斷為擊中(處理子彈擊中影片、子彈消失、僵尸扣除相應血量、擊中的音效等),僵尸血量歸零后會停止走動的影片,開啟新的倒地影片,倒地完成后洗掉僵尸,同時累計得到的分數;
  7. 當僵尸的位置和植物的位置交匯的時候,僵尸會停止行走的影片,開啟吃的影片(植物被扣除血量、僵尸吃的音效),植物血量歸零后,植物物件會被清理;
  8. 陽光有兩種產生方式,定時產生和向日葵產生,產生后會開啟往下飄的影片,飄到一定范圍后停止影片、開啟計數器(目前設定為10秒),計數歸零沒有此陽光依然未被點擊收集的話就會消失,在指定時間內點擊了該陽光(音效),則會開啟往左上角飛行的影片,到終點后陽光消失,陽光分增加(音效);
  9. 結束條件:1)僵尸觸及田的最左邊--判定為失敗,2)得分300--判定為勝利!

實作

繪制背景

	//繪制背景
	Plants.prototype.drawBG=function(){
		var image,img,sx=150,sy=0,sWidth=900,sHeight=600,dx=0,dy=0,dWidth=900,dHeight=600;
		//背景
		image = this.imgObj[1];
		img = new _.ImageDraw({image:image,sx:sx,sy:sy,sWidth:sWidth,sHeight:sHeight, dx:dx, dy:dy ,dWidth:dWidth,dHeight:dHeight});
		this.renderArr.push(img);
	}

繪制上方的卡牌區域、積磁區域,相關按鈕

//繪制游戲上方的相關圖片(卡片等)
	Plants.prototype.drawCard=function(){
		var image,img,sx=0,sy=0,sWidth=446,sHeight=87,dx=0,dy=0,dWidth=446,dHeight=80;
		//方形卡片盤
		image = this.imgObj[2];
		img = new _.ImageDraw({image:image,sx:sx,sy:sy,sWidth:sWidth,sHeight:sHeight, dx:dx, dy:dy ,dWidth:dWidth,dHeight:dHeight});
		this.renderArr.push(img);
		
		sWidth=128,sHeight=31,dx=450,dy=0,dWidth=128,dHeight=40;
		//積分
		image = this.imgObj[12];
		img = new _.ImageDraw({image:image,sx:sx,sy:sy,sWidth:sWidth,sHeight:sHeight, dx:dx, dy:dy ,dWidth:dWidth,dHeight:dHeight});
		this.renderArr.push(img);
		
		sWidth=50,sHeight=70,dx=76,dy=5,dWidth=50,dHeight=68;
		//方形卡片 太陽花
		image = this.imgObj[3];
		img = new _.ImageDraw({image:image,sx:sx,sy:sy,sWidth:sWidth,sHeight:sHeight, dx:dx, dy:dy ,dWidth:dWidth,dHeight:dHeight});
		img.sunCost=50;//生產一個苗需要50陽光
		img.type='sun';//植物型別
		this.renderArr.push(img);
		this.cardArr.push(img);
		
		sWidth=50,sHeight=70,dx=130,dy=4,dWidth=50,dHeight=70;
		//方形卡片 豌豆
		image = this.imgObj[4];
		img = new _.ImageDraw({image:image,sx:sx,sy:sy,sWidth:sWidth,sHeight:sHeight, dx:dx, dy:dy ,dWidth:dWidth,dHeight:dHeight});
		img.sunCost=100;//生產一個苗需要100陽光
		img.type='wandou';//植物型別
		this.renderArr.push(img);
		this.cardArr.push(img);

		
		sWidth=97,sHeight=33,dx=780,dy=8,dWidth=97,dHeight=33;
		//開始按鈕圖片
		image = this.imgObj[5];
		img = new _.ImageDraw({image:image,sx:sx,sy:sy,sWidth:sWidth,sHeight:sHeight, dx:dx, dy:dy ,dWidth:dWidth,dHeight:dHeight});
		this.startImage=img;
		this.renderArr.push(img);
		
		sWidth=97,sHeight=33,dx=650,dy=8,dWidth=97,dHeight=33;
		//創建僵尸圖片
		image = this.imgObj[8];
		img = new _.ImageDraw({image:image,sx:sx,sy:sy,sWidth:sWidth,sHeight:sHeight, dx:dx, dy:dy ,dWidth:dWidth,dHeight:dHeight});
		this.createZombiesImage=img;
		this.renderArr.push(img);
	}

點擊開始的邏輯

點擊開始就是游戲的入口,游戲的大部分功能都是在這個邏輯里面實作,包含

展示開始圖片、開啟背景音樂、陽光計分顯示、積分顯示、創建田的背景方形、創建卡牌的背景方形、開啟總任務、定時創建太陽光、定時創建僵尸,

展示開始圖片

//展示開始圖片
	Plants.prototype.startShow=function(){
		var image,img,sx=0,sy=0,sWidth=225,sHeight=108,dx=this.w/2-110,dy=this.h/2-100,dWidth=225,dHeight=108;
		image = this.imgObj[10];
		img = new _.ImageDraw({image:image,sx:sx,sy:sy,sWidth:sWidth,sHeight:sHeight, dx:dx, dy:dy ,dWidth:dWidth,dHeight:dHeight});
		this.renderArr2.push(img);
		
		var that=this;
		setTimeout(function(){
			that.clear(img);
		},2000);
	}

這里設定一個延時2秒后自動清除掉這個圖片

陽光計分顯示

//創建陽光分
	Plants.prototype.createSunText=function(){
		x=40,y=74,content=this.sunTotalCount;
		var	text = new _.Text({
			x:x,
			y:y,
			text:content,
			font:'20px ans-serif',
			textAlign:'center',
			fill:true,
			fillStyle:'green'
		});	
		this.renderArr2.push(text);	
		this.sunCountObj=text;
	}

積分顯示

	//創建積分
	Plants.prototype.createCountText=function(){
		x=530,y=34,content=this.curCount;
		var	text = new _.Text({
			x:x,
			y:y,
			text:content,
			font:'30px ans-serif',
			textAlign:'center',
			fill:true,
			fillStyle:'pink'
		});	
		this.renderArr2.push(text);	
		this.countObj=text;
	}

創建卡牌的背景方形(用于監聽滑鼠點擊卡牌)

根據卡牌陣列來創建,回圈這個陣列,方形的坐標和寬高與卡牌陣列元素相對應,并且方形的fillStyle采用rgba來處理,如:rgba(192,192,192,0) rgba(192,192,192,0.6),當最后一個數字是0的時候卡牌可用,當為0.6的時候,卡牌會被遮罩起來不可用(不可用是在滑鼠點擊的時候控制,這里只是一個遮罩的效果),當然這里會設定一個引數alive,當它為true表示可用,false則點擊無效,滑鼠點擊的時候就是根據這個引數來控制的,

	//創建卡片背景方形
	Plants.prototype.createCardBGRect=function(){
		var x=0,y=0,rect,fillStyle,alive;
		for(var i=0;i<this.cardArr.length;i++){
			var item=this.cardArr[i];
			fillStyle = this.sunTotalCount>=item.sunCost? 'rgba(192,192,192,0)':'rgba(192,192,192,0.5)';
			alive = this.sunTotalCount>=item.sunCost? true:false;
			rect = new _.Rect({
				x:item.dx,
				y:item.dy,
				width:item.dWidth,
				height:item.dHeight,
				fill:true,
				fillStyle:fillStyle
			 })
			 rect.sunCost=item.sunCost;//設定需要花費的陽光數值
			 rect.alive=alive;
			 rect.type=item.type;
			 this.renderArr2.push(rect);
			 this.cardRectArr.push(rect);
		}
		
	}

創建田的背景方形(用于監聽植物的種植)

根據背景上田的規格,來設定好X、Y坐標以及寬高,這樣創建的方形就會和背景相對應,種植物的時候就比較好控制了,解釋如下:

上圖是我自己隨便畫的,也沒有畫好、沒畫全,實際上每個里面都有,并且比較整齊,我把代碼稍微修改一下截圖,最終的代碼肯定不是這樣的哦

這樣,就把田的區域一塊塊的覆寫起來,但我們這里也是要用rgba的方式來,種植物的時候才會突出顯示

//創建植物田背景方形
	Plants.prototype.createBGRect=function(){
		var x=0,y=0,rect;
		for(var i=1;i<=5;i++){//5行
			y = 75+(i-1)*100;
			for(var j=1;j<=9;j++){//9列
				x = 105+(j-1)*80;
				rect = new _.Rect({
					x:x,
					y:y,
					width:80,
					height:100,
					fill:true,
					//fillStyle:_.getRandomColor()
					fillStyle:'rgba(0,250,154, 0)'
				 })
				 rect.index=i;//標記行數
				 this.renderArr2.push(rect);
				 this.bgRectArr.push(rect);
			}
		}
	}

創建陽光

1、向日葵植物創建陽光和定時創建陽光都放到這里了,他們的區別是:定時創建的X坐標隨機產生,而向日葵創建的陽光X、Y坐標是根據向日葵的位置來的,

2、設定陽光的分值、陽光的血量、陽光默認運動的終點位置(這個位置可以自己定,我定義Y坐標的是400),陽光為什么有血量呢?這個血量是用來控制消失時間的,比如我設定血量為100,當陽光運動到底部停止運動后,就會開啟計算血量的任務,每100毫秒執行一次,讓血量 -1,因100毫秒執行10次是1秒,1秒后血量就變成90了,當血量歸零后如若依然沒有去收集這個陽光,需要讓陽光消失,同時關閉此定時器,

3、當點擊陽光后,就把計算血量的定時器關閉,開啟向左上角運動的影片,當然這里要用到 Math.atan2 根據陽光點擊的位置和做上角的位置,計算出角度,然后根據角度利用Math.cos、Math.sin 計算出運動的X、Y的數值,定時器將根據這個數值來運動,

4、運動指定的位置后,要做以下動作:清除運動定時器、陽光積分累加與顯示、收集音效開啟、陽光消失、卡牌可用狀態的更新,

//創建陽光
	Plants.prototype.createSun=function(plant){
		var image,sun,sx=0,sy=0,sWidth=77,sHeight=74,dx=0,dy=70,dWidth=45,dHeight=44;
		
		if(plant){//這種是植物創建的太陽
			dx = plant.dx;
			dy = plant.dy;
		}else{
			dx = _.getRandom(200,800);//x方形隨機200-800
		}
		
		//繪制時的圖片下標
		var startKey=this.count+this.zombiesRunCount+this.wandousRunCount+this.zombiesDeadCount+1;
		//方形卡片盤
		image = this.imgObj[startKey];
		sun = new _.ImageDraw({image:image,sx:sx,sy:sy,sWidth:sWidth,sHeight:sHeight, dx:dx, dy:dy ,dWidth:dWidth,dHeight:dHeight});
		sun.imageKey=startKey;//執行影片更新的下標
		sun.key=startKey;//原始下標
		sun.value=20;//收集一個20分
		sun.blood=100;//默認10點血量(10秒消失,因為我給太陽設定的是100毫秒執行一次,所以這個blood設定為100,每次-1 ,10秒就剛好100)
		sun.floor=400;//到底的位置
		this.renderArr2.push(sun);
		
		this.suns.push(sun);
		
		sun.timmer = setInterval(animate.bind(this,sun),100);
		function animate(z){
			var that=this;
			z.imageKey ++;
			//一個回圈了,重新回到初始位置
			if(z.imageKey>=(that.sunRunCount+z.key)){
				z.imageKey=z.key;
			}
			z.image = that.imgObj[z.imageKey];
			z.dy+=2;
			if(z.dy>=z.floor){
				//console.log('太陽到位置了');
				clearInterval(z.timmer);
				//開啟定時任務,多少秒以后消失
				sun.timmer = setInterval(time.bind(this,sun),100);
			}
		}
		
		//太陽計時
		function time(z){
			var that=this;
			z.imageKey ++;
			//一個回圈了,重新回到初始位置
			if(z.imageKey>=(that.sunRunCount+z.key)){
				z.imageKey=z.key;
			}
			z.image = that.imgObj[z.imageKey];
			//計算消失時間
			z.blood--;
			if(z.blood<=0){
				clearInterval(z.timmer);
				//console.log('太陽到時間了');
				fade.call(this,z);//執行消失
			}
		}
		
		//太陽消失
		function fade(z){
			this.clear(z);
			//console.log('太陽消失了');
			this.clearAssign(this.suns,z);//清楚指定物件
			z=null;
		}
		//太陽被點擊
		function sunClick(z){
			//console.log('太陽被點擊了')
			clearInterval(z.timmer);//清楚之前的定時器
			
			this.pointsMusic.play();
			
			var cx=cy=20;//收集點的X\Y坐標
			var angle = Math.atan2((z.dy-cy), (z.dx-cx))  //弧度 
			
			//計算出X\Y每一幀移動的距離
			var mx = my=0;
			mx = Math.cos(angle)*20;
			my = Math.sin(angle)*20;
			z.mx=mx,z.my=my;
			//開啟移動定時器
			z.timmer = setInterval(sunCollect.bind(this,z),100);
		}
		//收集太陽影片
		function sunCollect(z){
			var that=this;
			z.imageKey ++;
			//一個回圈了,重新回到初始位置
			if(z.imageKey>=(that.sunRunCount+z.key)){
				z.imageKey=z.key;
			}
			z.image = that.imgObj[z.imageKey];
			z.dx-=z.mx;
			z.dy-=z.my;
			if(z.dy<=20||z.dx<=20){
				//console.log('太陽收集完成');
				clearInterval(z.timmer);
				
				this.moneyfallsMusic.play();
				
				fade.call(this,z);//執行消失
				//計數累加
				that.sunTotalCount+=z.value;
				
				//更新卡片是否可用情況
				that.updateCardUse();
				//更新陽光數值
				that.sunCountObj.text=that.sunTotalCount;
			}
		}
		
		sun.click=sunClick.bind(this,sun);//給這個sun物件系結點擊函式
	}

創建僵尸

1、創建僵尸:定時(10秒)、亂數量(1-5只)、隨機行(1-5行,這里表示出現在地圖的哪一行),

2、設定僵尸的血量、僵尸所處的行數、僵尸的狀態(run、eat、dead),狀態是用來控制切換圖片的下標的,不然影片會出錯,

3、行走影片依賴于圖片的切換和X坐標的改變(每一幀x坐標 減少2即可),

4、每一幀都要判斷x坐標與植物的坐標是否交匯,如果是先關閉行走的影片,更新狀態為 eat ,開啟吃的影片,切換圖片的下標,

5、每一次吃的時候遞減植物的血量,判斷植物的血量,如果血量歸零則表示吃完了,此時要清理掉植物,僵尸回歸行走的影片,狀態改為run;若沒吃完則繼續吃,

6、每一幀也要判斷僵尸的x坐標是否到了最左邊,如果是游戲結束,

創建

	//創建僵尸
	Plants.prototype.createZombie=function(){
		var image,zomble,sx=0,sy=0,sWidth=75,sHeight=119,dx=900-75,dy=270,dWidth=75,dHeight=119;
		
		var index = _.getRandom(1,6);//隨機獲取1\2\3\4\5 行數
		if(index==1){
			dy=60;
		}else if(index==2){
			dy=160;
		}else if(index==3){
			dy=260;
		}else if(index==4){
			dy=355;
		}else if(index==5){
			dy=460;
		}
		
		//繪制時的圖片下標
		var startKey=this.count+1;
		//方形卡片盤
		image = this.imgObj[startKey];
		zomble = new _.ImageDraw({image:image,sx:sx,sy:sy,sWidth:sWidth,sHeight:sHeight, dx:dx, dy:dy ,dWidth:dWidth,dHeight:dHeight});
		zomble.imageKey=startKey;//執行影片更新的下標
		zomble.key=startKey;//原始下標
		zomble.blood=10;//默認10點血量
		zomble.index=index;//設定僵尸的行數
		zomble.state='run';
		this.renderArr2.push(zomble);
		this.zombies.push(zomble);
		
		zomble.run=run.bind(this);
		
		zomble.run();
		
		function run(){
			zomble.timmer = setInterval(animate.bind(this,zomble),100);
		}
		function animate(z){
			var that=this;
			z.imageKey ++;
			//一個回圈了,重新回到初始位置
			if(z.imageKey>=(that.zombiesRunCount+z.key)){
				z.imageKey=z.key;
			}
			z.image = that.imgObj[z.imageKey];
			z.dx-=2;
			//判斷有沒有接觸到植物,如果有開始吃植物 
			that.eat(zomble)
			
			if(z.dx<=100){
				console.log('結束了');
				that.end();
			}	
		}
	}

僵尸吃

//僵尸吃
	Plants.prototype.eat=function(zomble){
		//先判斷當前僵尸有沒有到達吃的位置,有的話就開始吃,關閉掉之前的僵尸影片,開始吃的影片
		var plants=this.plants;
		var plant;//被捕獲的植物
		for(var i=0;i<plants.length;i++){
			var item=plants[i];
			if(item.index==zomble.index){
				if(item.dx+item.dWidth-20>=zomble.dx){//判斷為吃
					plant=item;
					break;
				}
			}
			
		}
		if(plant){
			clearInterval(zomble.timmer);//清除移動影片
			zomble.imageKey=zomble.key=this.count+this.zombiesRunCount+this.wandousRunCount+this.zombiesDeadCount+this.sunRunCount+1;//設定key
			zomble.state='eat';
			zomble.timmer = setInterval(animate.bind(this,zomble,plant),100);
		}
		
		function animate(z,p){
			this.eatMusic.play();
			var that=this;
			z.imageKey ++;
			//一個回圈了,重新回到初始位置
			if(z.imageKey>=(that.zombiesEatCount+z.key)){
				z.imageKey=z.key;
			}
			z.image = that.imgObj[z.imageKey];
			
			p.blood--;//植物血量的處理
			if(p.blood<=0){
				//console.log('植物被吃了');
				clearInterval(z.timmer);
				//清除植物
				this.delPlant(p);
				zomble.state='run';
				zomble.imageKey=zomble.key=this.count+1;//設定key
				//繼續移動
				z.run();
			}
		}
	}

洗掉植物

//洗掉掉植物
	Plants.prototype.delPlant=function(plant,type){
		if(!type){//還沒有創建的植物不需要清除這兩個任務
			//停止植物自身的影片
			clearInterval(plant.timmerSelf);
			//停止植物發射子彈的影片
			clearInterval(plant.timmer);
		}
		//渲染中洗掉
		this.clear(plant);
		//plants陣列中洗掉
		this.clearAssign(this.plants,plant);
		//植物對應的背景處理
		if(plant.bgRect){
			plant.bgRect.alive=false;
			plant.bgRect.plant=false;
		}
		plant=null;
	}

創建向日葵

1、點擊卡牌后的創建,所以肯定是要傳入滑鼠的位置,創建的跟隨滑鼠移動(此時向日葵已經創建)

2、設定 alive 函式,當在田里面選中位置后,執行此函式,進行種植操作,

3、扣除陽光花費、更新卡牌是否可用、開啟定時產生陽光的任務,

	//創建太陽植物
	Plants.prototype.createSunPlant=function(pos,item){
		var image,plant,sx=0,sy=0,sWidth=63,sHeight=73,dx=110,dy=300,dWidth=63,dHeight=73;
		dx = pos.x,dy=pos.y;//設定初始位置為滑鼠的位置
		//繪制時的圖片下標
		var startKey=this.count+this.zombiesRunCount+this.wandousRunCount+this.zombiesDeadCount+this.sunRunCount+this.zombiesEatCount+1;
		//方形卡片盤
		image = this.imgObj[startKey];
		plant = new _.ImageDraw({image:image,sx:sx,sy:sy,sWidth:sWidth,sHeight:sHeight, dx:dx, dy:dy ,dWidth:dWidth,dHeight:dHeight});
		plant.imageKey=startKey;//執行影片更新的下標
		plant.key=startKey;//原始下標
		plant.sunCost=item.sunCost;//陽光花費值
		plant.blood=50;//設定為50血量,實際是5秒吃完,因為100毫秒計算一次吃
		plant.id='SunPlant';
		this.renderArr2.push(plant);
		
		this.plants.push(plant);
		this.currPlant=plant;//標記正在創建的植物
		
		plant.alive=alive.bind(this);
		
		function alive(bgRect){
			//設定背景物件
			plant.bgRect=bgRect;
			//扣除陽光花費
			this.sunTotalCount-=plant.sunCost;
			//更新卡片是否可用情況
			this.updateCardUse();
			//更新陽光數值
			this.sunCountObj.text=this.sunTotalCount;
			//每6秒發射一個陽光
			plant.timmer = setInterval(shoot.bind(this),6000);
			this.plantMusic.play();//音樂
		}
		
		function shoot(){
			this.createSun(plant);
		}
		//植物本身的影片
		plant.timmerSelf = setInterval(animate.bind(this,plant),100);
		function animate(p){
			var that=this;
			p.imageKey ++;
			//一個回圈了,重新回到初始位置
			if(p.imageKey>=(that.sunPlantRunCount+p.key)){
				p.imageKey=p.key;
			}
			p.image = that.imgObj[p.imageKey];
		}
	}

點擊卡牌后移動滑鼠,向日葵會跟隨滑鼠移動,移動到田的方形位置,則此塊方形的顏色會突出,點擊它則會種植下去,

創建豌豆植物

1、與向日葵的創建很相似,就不同的時候向日葵創建的是陽光,豌豆植物創建的是小豌豆,可以攻擊僵尸的,

2、子彈在運動的時候、判斷是否與僵尸接觸,如果接觸則執行擊中的影片、洗掉這個子彈、減去僵尸的血量、如果僵尸死亡,則開啟僵尸死亡影片,增加積分,

創建

//創建豌豆
	Plants.prototype.createWandou=function(plant){
		var image,img,sx=0,sy=0,sWidth=28,sHeight=28,dx=plant.dx+50,dy=plant.dy,dWidth=28,dHeight=28;
		//繪制時的圖片下標
		var startKey=6;
		//方形卡片盤
		image = this.imgObj[startKey];
		var wandou= new _.ImageDraw({image:image,sx:sx,sy:sy,sWidth:sWidth,sHeight:sHeight, dx:dx, dy:dy ,dWidth:dWidth,dHeight:dHeight});
		this.renderArr2.push(wandou);
		
		wandou.index=plant.index;//給子彈設定行數標示
		
		this.shootMusic.play();//射擊音樂
		wandou.timmer = setInterval(wandouMove.bind(this,wandou),100);

		function wandouMove(wandou){
			wandou.dx+=10;
			//判斷擊中僵尸
			var flag = this.hit(wandou);
			if(flag || wandou.dx>850){//擊中目標 或者 超出邊界
				clearInterval(wandou.timmer);
				if(flag){//擊中目標
					//創建子彈擊中的圖片
					this.hitAnimate(wandou);
					this.hitMusic.play();//擊中音樂
				}
				//清除這個子彈
				this.clear(wandou);
				wandou=null;
			}
		}
	}

擊中目標

//擊中
	Plants.prototype.hit=function(obj){
		var arr = this.zombies;//僵尸物件
		for(var i=0;i<arr.length;i++){
			var item = arr[i];
			if(item.dx<=obj.dx+obj.dWidth && obj.index==item.index ){//子彈的函式標示與僵尸的行數標示也要相等
				item.blood--;
				if(item.blood==0){//消滅這個僵尸
					clearInterval(item.timmer);
					if(item.state=='run'){
						item.imageKey=item.key=item.key+this.zombiesRunCount+this.wandousRunCount;//設定死亡圖片下標
					}else if(item.state=='eat'){
						item.imageKey=item.key=item.key-this.sunRunCount-this.zombiesDeadCount;//設定死亡圖片下標
					}
					
					item.state='dead';
					this.addCount();//增加積分
					this.dead(item);//死亡影片
					arr.splice(i,1);
				}
				return true;
			}
		}
	}

加入事件控制

        //給canvas2畫布添加滑鼠移動事件(因為畫布2在上面)
		canvas2.addEventListener('mousemove',this.mouseMove.bind(this));
		
		//給canvas2畫布添加滑鼠點擊事件(因為畫布2在上面)
		canvas2.addEventListener('click',this.mouseClick.bind(this));
		
		//給canvas2畫布添加滑鼠右鍵事件(因為畫布2在上面)
		canvas2.addEventListener('contextmenu',this.contextMenu.bind(this));

滑鼠移動事件

//滑鼠移動事件
	Plants.prototype.mouseMove=function(e){
		var that=this;
		if(that.gameOver) return ;//目前設定的是結束后,要重繪頁面才可以開始
		if(!this.startImage) return; //防止沒加載完成報錯
		var pos = _.getOffset(e);//獲取滑鼠位置
		var isCatch = this.startImage.isPoint(pos);//滑鼠捕捉
		
		if(!isCatch  && this.gameAlive){
			isCatch = this.createZombiesImage.isPoint(pos);//滑鼠捕捉
		}
		
		if(!isCatch){
			if(this.gameAlive && !this.currPlant) {//游戲開始,并且沒有正在創建的植物的時候可以執行
				//回圈卡片背景陣列
				for(var i=0;i<this.cardRectArr.length;i++){
					var item=this.cardRectArr[i];
					if(item.isPoint(pos) && item.alive){//滑鼠捕捉
						isCatch=true;
						break;
					}
				}
			}
		}
		if(!isCatch){
			//回圈太陽陣列
			for(var i=0;i<this.suns.length;i++){
				var item=this.suns[i];
				if(item.isPoint(pos)){//滑鼠捕捉
					isCatch=true;
					break;
				}
			}
		}
		
		var plant = this.currPlant;
		if(!isCatch){
			if(plant){//創建植物的時候,才出現填入背景框
				//回圈田背景陣列
				for(var i=0;i<this.bgRectArr.length;i++){
					var item=this.bgRectArr[i];
					if(item.isPoint(pos) && !item.plant){//滑鼠捕捉,并且當前沒有植物
						isCatch=true;
						item.fillStyle="rgba(0,250,154, 0.5)";
					}else{
						item.fillStyle="rgba(0,250,154, 0)";
					}
				}
			}
		}
		
		if(isCatch){
			this.el.style.cursor = 'pointer';//改為手狀形態
		}else{
			this.el.style.cursor = '';
		}
		//表示當前正在創建植物
		if(plant){
			plant.dx=pos.x;
			plant.dy=pos.y;
		}
		
		this.render2();
	}

滑鼠點擊事件

//滑鼠點擊事件
	Plants.prototype.mouseClick=function(e){
		if(this.gameOver) return ;//目前設定的是結束后,要重繪頁面才可以開始
		var that=this;
		var pos = _.getOffset(e);//獲取滑鼠位置
		var isCatch = that.startImage.isPoint(pos);//滑鼠捕捉
		if(isCatch){
			that.start();
		}
		if(!isCatch && this.gameAlive){
			isCatch = this.createZombiesImage.isPoint(pos);//滑鼠捕捉
			if(isCatch){
				that.createZombie();
			}
		}
		if(!isCatch){
			if(this.gameAlive && !this.currPlant) {//游戲開始,并且沒有正在創建的植物的時候可以執行
				//回圈卡片陣列
				for(var i=0;i<this.cardRectArr.length;i++){
					var item=this.cardRectArr[i];
					if(item.isPoint(pos) && item.alive){//滑鼠捕捉
						isCatch=true;
						//生成一個新的豌豆苗,跟隨滑鼠移動
						this.createPlant(pos,item);//創建植物
						break;
					}
				}
			}
		}
		
		if(!isCatch){
			//回圈太陽陣列
			for(var i=0;i<this.suns.length;i++){
				var item=this.suns[i];
				if(item.isPoint(pos)){//滑鼠捕捉
					item.click && item.click();
					break;
				}
			}
		}
		if(!isCatch){
			var plant = this.currPlant;
			if(plant){//正在創建植物
				//回圈田背景陣列
				for(var i=0;i<this.bgRectArr.length;i++){
					var item=this.bgRectArr[i];
					if(item.plant) continue;//如果當前有植物,則跳過
					if(item.isPoint(pos)){//滑鼠捕捉
						isCatch=true;
						plant.dx=item.x+10;
						plant.dy=item.y+20;
						plant.index=item.index;//給plant設定行數
						plant.alive(item);//植物生效
						
						item.plant=true;//表示有植物
						item.fillStyle="rgba(0,250,154, 0)";
						
						this.currPlant=null;
						break;
					}
				}
			}
		
		}	
	}

滑鼠右鍵事件

//右鍵事件
	Plants.prototype.contextMenu=function(e){
		var e = e||window.event;
			//取消右鍵默認事件
		e.preventDefault && e.preventDefault();
		if(this.gameOver) return ;//目前設定的是結束后,要重繪頁面才可以開始
		if(!this.startImage) return; //防止沒加載完成報錯
		if(!this.gameAlive)return;
				console.log('oncontextmenu');
		//正在創建的植物洗掉
		this.delPlant(this.currPlant,1);
		this.currPlant=null;
		//回圈田背景陣列
		for(var i=0;i<this.bgRectArr.length;i++){
			var item=this.bgRectArr[i];
			item.fillStyle="rgba(0,250,154, 0)";
		}
	}

總結

基本的功能實作了,但是有很多沒有實作的功能這里交代一下,確實沒有時間去搞了,要上班,,,,,,不能像在座賺了幾個億的大佬一樣逍遙自在:

1、結束后必須重繪頁面,才能重新開始(”點擊開始“按鈕),

2、卡牌支持的作物也比較少,作物也不能用鏟子替換,

3、沒有過關的感覺、沒有車子壓僵尸的場景等等(不說了還有好多...),

如果作為完整的游戲來說,肯定還有很多功能要做、很多地方要完善、其中相當的代碼也可以重構;但是如果作為一個練手、學習、思路的分享,我覺得是足夠了,

昨天做好的東西,今天晚上寫出來也花了不少時間,能看到這里的都是大佬,

歡迎各位大佬 點贊+評論+關注,謝謝!

原始碼下載

方式1:少量積分,下載代碼

方式2:關注下方公眾號,回復 129 下載代碼

更多原始碼

? 基于canvas的九宮格抽獎特效(附原始碼)?

? 基于canvas的手風琴特效(附原始碼)?

? 抖音很火的華為太空人表盤(附原始碼)?

? 基于JavaScript頁面動態驗證碼(附原始碼)?

? 基于JavaScript的拖動滑塊拼圖驗證(附原始碼)?

? 基于JavaScript的幸運大轉盤(附原始碼)?

? 抖音很火的羅盤時鐘(附原始碼)?

? 基于JavaScript的俄羅斯方塊小游戲(附原始碼)?

? 基于JavaScript的貪吃蛇游戲(附原始碼)?

? 基于JavaScript的拼圖游戲(附原始碼)?

? 用JavaScript給女兒做的煙花特效(附原始碼)?

? 老父親給女兒做的下雪特效,滿足女兒看雪的愿望(附原始碼)?

? 雷達掃描特效(附原始碼)?

? 香港黃金配角吳孟達去世,80后程式員以輪播圖來悼念達叔,達叔一路走好!(附原始碼)?

? 仿抖音重繪進度條(附原始碼)?

? 仿頭條方形重繪進度條(附原始碼)?

? 仿360加速球、水波評分特效(附原始碼)?

? 基于canvas的刮刮卡(附原始碼)?

? 原生js寫的左側飛入拼圖特效,你是喜歡美女單飛還是雙飛(附原始碼)?

? 用js寫的旋轉木馬,在新年獻給各位剛登基的皇帝,讓你的后宮轉起來!程式員就是可以為所欲為!(附原始碼)?

? 用js寫的輪播圖,八位女明星,你翻誰的牌,程式員就是可以為所欲為!(附原始碼)?

? 原生js實作美女拼圖,把美女老婆抱回家,5個美女夠不夠!程式員就是可以為所欲為!(附原始碼)?

? 用js仿探探拖拽卡片的效果、飛卡片的效果,感覺挺酷,最后有美女看哦!(附原始碼)?

? 老婆說程式員不懂浪漫,程式員默默拿起了鍵盤,這就親手帶你去看流星雨,女人真的會影響男人拔刀的速度!(附原始碼)?

? 學生成績管理系統(jsp+jquery+java+mysql+tomcat)有原始碼,你的畢設我的心(附原始碼)?

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/278490.html

標籤:其他

上一篇:純CSS、JS簡單實作圖片輪播效果

下一篇:前端代碼——前端代碼規范(含html、css、javascript、vue等)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • vue移動端上拉加載

    可能做得過于簡單或者比較low,請各位大佬留情,一起探討技術 ......

    uj5u.com 2020-09-10 04:38:07 more
  • 優美網站首頁,頂部多層導航

    一個個人用的瀏覽器首頁,可以把一下常用的網站放在這里,平常打開會比較方便。 第一步,HTML代碼 <script src=https://www.cnblogs.com/szharf/p/"js/jquery-3.4.1.min.js"></script> <div id="navigate"> <ul> <li class="labels labels_1"> ......

    uj5u.com 2020-09-10 04:38:47 more
  • 頁面為要加<!DOCTYPE html>

    最近因為寫一個js函式,需要用到$(window).height(); 由于手寫demo的時候,過于自信,其實對前端方面的認識也不夠體系,用文本檔案直接敲出來的html代碼,第一行沒有加上<!DOCTYPE html> 導致了$(window).height();的結果直接是整個document的高 ......

    uj5u.com 2020-09-10 04:38:52 more
  • WordPress網站程式手動升級要做好資料備份

    WordPress博客網站程式在進行升級前,必須要做好網站資料的備份,這個問題良家佐言是遇見過的;在剛開始接觸WordPress博客程式的時候,因為升級問題和博客網站的修改的一些嘗試,良家佐言是吃盡了苦頭。因為購買的是西部數碼的空間和域名,每當佐言把自己的WordPress博客網站搞到一塌糊涂的時候 ......

    uj5u.com 2020-09-10 04:39:30 more
  • WordPress程式不能升級為5.4.2版本的原因

    WordPress是一款個人博客系統,受到英文博客愛好者和中文博客愛好者的追捧,并逐步演化成一款內容管理系統軟體;它是使用PHP語言和MySQL資料庫開發的,用戶可以在支持PHP和MySQL資料庫的服務器上使用自己的博客。每一次WordPress程式的更新,就會牽動無數WordPress愛好者的心, ......

    uj5u.com 2020-09-10 04:39:49 more
  • 使用CSS3的偽元素進行首字母下沉和首行改變樣式

    網頁中常見的一種效果,首字改變樣式或者首行改變樣式,效果如下圖。 代碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, ......

    uj5u.com 2020-09-10 04:40:09 more
  • 關于a標簽的講解

    什么是a標簽? <a> 標簽定義超鏈接,用于從一個頁面鏈接到另一個頁面。 <a> 元素最重要的屬性是 href 屬性,它指定鏈接的目標。 a標簽的語法格式:<a href=https://www.cnblogs.com/summerxbc/p/"指定要跳轉的目標界面的鏈接">需要展示給用戶看見的內容</a> a標簽 在所有瀏覽器中,鏈接的默認外觀如下: 未被訪問的鏈接帶 ......

    uj5u.com 2020-09-10 04:40:11 more
  • 前端輪播圖

    在需要輪播的頁面是引入swiper.min.js和swiper.min.css swiper.min.js地址: 鏈接:https://pan.baidu.com/s/15Uh516YHa4CV3X-RyjEIWw 提取碼:4aks swiper.min.css地址 鏈接:https://pan.b ......

    uj5u.com 2020-09-10 04:40:13 more
  • 如何設定html中的背景圖片(全屏顯示,且不拉伸)

    1 <style>2 body{background-image:url(https://uploadbeta.com/api/pictures/random/?key=BingEverydayWallpaperPicture); 3 background-size:cover;background ......

    uj5u.com 2020-09-10 04:40:16 more
  • Java學習——HTML詳解(上)

    HTML詳解 初識HTML Hyper Text Markup Language(超文本標記語言) 1 <!--DOCTYPE:告訴瀏覽器我們要使用什么規范--> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <!--meta 描述性的標簽,描述一些 ......

    uj5u.com 2020-09-10 04:40:33 more
最新发布
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 07:59:23 more
  • 生產事故-走近科學之消失的JWT

    入職多年,面對生產環境,盡管都是小心翼翼,慎之又慎,還是難免捅出簍子。輕則滿頭大汗,面紅耳赤。重則系統停擺,損失資金。每一個生產事故的背后,都是寶貴的經驗和教訓,都是專案成員的血淚史。為了更好地防范和遏制今后的各類事故,特開此專題,長期更新和記錄大大小小的各類事故。有些是親身經歷,有些是經人耳傳口授 ......

    uj5u.com 2023-04-18 07:55:04 more
  • 記錄--Canvas實作打飛字游戲

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 打開游戲界面,看到一個畫面簡潔、卻又富有挑戰性的游戲。螢屏上,有一個白色的矩形框,里面不斷下落著各種單詞,而我需要迅速地輸入這些單詞。如果我輸入的單詞與螢屏上的單詞匹配,那么我就可以獲得得分;如果我輸入的單詞錯誤或者時間過長,那么我就會輸 ......

    uj5u.com 2023-04-04 08:35:30 more
  • 了解 HTTP 看這一篇就夠

    在學習網路之前,了解它的歷史能夠幫助我們明白為何它會發展為如今這個樣子,引發探究網路的興趣。下面的這張圖片就展示了“互聯網”誕生至今的發展歷程。 ......

    uj5u.com 2023-03-16 11:00:15 more
  • 藍牙-低功耗中心設備

    //11.開啟藍牙配接器 openBluetoothAdapter //21.開始搜索藍牙設備 startBluetoothDevicesDiscovery //31.開啟監聽搜索藍牙設備 onBluetoothDeviceFound //30.停止監聽搜索藍牙設備 offBluetoothDevi ......

    uj5u.com 2023-03-15 09:06:45 more
  • canvas畫板(滑鼠和觸摸)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>canves</title> <style> #canvas { cursor:url(../images/pen.png),crosshair; } #canvasdiv{ bo ......

    uj5u.com 2023-02-15 08:56:31 more
  • 手機端H5 實作自定義拍照界面

    手機端 H5 實作自定義拍照界面也可以使用 MediaDevices API 和 <video> 標簽來實作,和在桌面端做法基本一致。 首先,使用 MediaDevices.getUserMedia() 方法獲取攝像頭媒體流,并將其傳遞給 <video> 標簽進行渲染。 接著,使用 HTML 的 < ......

    uj5u.com 2023-01-12 07:58:22 more
  • 記錄--短視頻滑動播放在 H5 下的實作

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 短視頻已經無數不在了,但是主體還是使用 app 來承載的。本文講述 H5 如何實作 app 的視頻滑動體驗。 無聲勝有聲,一圖頂百辯,且看下圖: 網址鏈接(需在微信或者手Q中瀏覽) 從上圖可以看到,我們主要實作的功能也是本文要講解的有: ......

    uj5u.com 2023-01-04 07:29:05 more
  • 一文讀懂 HTTP/1 HTTP/2 HTTP/3

    從 1989 年萬維網(www)誕生,HTTP(HyperText Transfer Protocol)經歷了眾多版本迭代,WebSocket 也在期間萌芽。1991 年 HTTP0.9 被發明。1996 年出現了 HTTP1.0。2015 年 HTTP2 正式發布。2020 年 HTTP3 或能正... ......

    uj5u.com 2022-12-24 06:56:02 more
  • 【HTML基礎篇002】HTML之form表單超詳解

    ??一、form表單是什么

    ??二、form表單的屬性

    ??三、input中的各種Type屬性值

    ??四、標簽 ......

    uj5u.com 2022-12-18 07:17:06 more