妈妈!我也会做植物大战僵尸啦!

写在前面

本文参考了课程先辈留下来的报告,在前人的基础上进行了代码框架和逻辑的整合,希望对大家有所帮助!

会实现PVZ的哪些功能?

PVZ逻辑示意图

  1. 阳光逐渐下落、触地逐渐消失
  2. 僵尸随机出现在某一行并向前移动
  3. 僵尸判断当前格子有无植物,并吃掉
  4. 点击豌豆卡片、选择豌豆、将豌豆种下
  5. 豌豆判断当前行、右边是否有僵尸,并射击

准备素材

开始前,先将所需的素材拖入An的库中,并为每个素材创建链接。
在这里插入图片描述
这是我们这个demo所需的所有素材,现在来详细讲讲:

  1. plantMc:这是我们最终种下的植物的样子(前后摇摆的豌豆)
  2. selectorMc:直译意思是选择器,就是我们决定把植物种在某个格子内时会出现的影子
  3. sunMc:太阳的样子
  4. bulletMc:子弹的样子
  5. zombieMc:僵尸的样子
  6. plantMovingMc:选择卡片后我们拖动的植物的样子
  7. peaShooter:豌豆的卡片

功能模块拆分实现

这个demo不是很难!大家冲鸭!!
下面将用朴实的语言来描述各个功能以及其代码实现。建议大家在看下面内容之前概览一下
PVZ逻辑示意图
以清晰的知道每一步处于框架中的哪个位置。

看看主函数

首先我们来看看Main函数中包含什么。
在这里插入图片描述

  1. steupField():游戏区域设置,并初始化,即创建一个5×9的数组
  2. drawField():将各个容器添加到舞台,创建金钱显示区域
  3. addZombies():每隔一段时间创建一个僵尸,放置在屏幕右边外侧
  4. addPlants():创建一个植物卡片,在卡片上添加点击侦听事件
  5. fallingSuns():每隔一段时间创建一个太阳
  6. stage.addEventListener(“tick”,onEnterFrm):遍历植物->存储子弹->遍历子弹,遍历僵尸,遍历太阳

初始化一些变量

不妨先把背景图在An中放入舞台,试一试图片大小,量一下相关尺寸。

var plantsArray = new Array()//种植下的植物数组
var zombiesArray = new Array();//出现的僵尸数组
//
//行高和列宽
//
var gridHeight = 115;
var gridWidth = 95;
var borderTop = 110;//bordertop
var borderLeft = 320;//borderleft
//
// 容器
//
var sunContainer = new cjs.Container();// 阳光容器
var plantContainer = new cjs.Container();// 植物容器
var bulletContainer = new cjs.Container();// 子弹容器
var zombieContainer = new cjs.Container();// 僵尸容器
var overlayContainer = new cjs.Container();// 覆盖物容器
//
// 种植物会用到的
//
var movingPlant;// 拖动中的植物
var selector;// 选择器(影子)
//
// 其他
//
var money = 100;// 金钱总量
var moneyText = new cjs.Text("Money:"+money,"50px Times","#000000")  ;// 动态文本框,用来显示玩家的金钱
var playerMoving = false;// 标志玩家是否在移动一个植物
var totalZombies = 0;

接下来,我们就要用这些变量做一些事情了!

初始化区域 setupField()和drawField()

在这里插入图片描述

//
// 游戏区域设置,创建用来存储植物和僵尸信息的数组
//
function setupField(){

	for (var i = 0; i < 5; i++) {
		plantsArray[i] = new Array();
		zombiesArray[i] = new Array();
		for (var j=0; j<9; j++) {
			plantsArray[i][j] = 0;
		}
	}
}

//
// 画出游戏区域
//
function drawField(){
	stage.addChild(sunContainer);
	stage.addChild(plantContainer);
	stage.addChild(bulletContainer);
	stage.addChild(zombieContainer);
	stage.addChild(overlayContainer);
	//Container前不能加stage.
	overlayContainer.addChild(moneyText);
	updateMoney();
	moneyText.textColor = 0x000000;
	moneyText.height=20;
}
//
// 更新显示阳光值
// 每次变量money发生变化时都要记得更新
//
function updateMoney(){
	moneyText.text = money.toString();
}

此时,我们就拥有了土地和钱,虽然它们现在看不到摸不着。

造僵尸!

在这里插入图片描述
创建一个计时器,每隔3000ms会让newZombie()这个函数执行一次。

//
// 初始化僵尸
//
function addZombies(){
	var zombieTimer = setInterval(newZombie, 3000);
}

//
// 增加一个新的僵尸
//
function newZombie(TimerEvent){
	var zombie = new lib.zombieMc();// 构造僵尸
	totalZombies++;
	zombieContainer.addChild(zombie);// 添加僵尸
	zombie.zombieRow=Math.floor(Math.random()*5);// 随机行数
	zombie.name="zombie_"+totalZombies;//僵尸取名
	zombiesArray[zombie.zombieRow].push(zombie.name);// 增加第row行的僵尸
	zombie.x=1000;// 把僵尸放在屏幕的右边
	zombie.y=zombie.zombieRow*gridHeight+borderTop;//把僵尸放到对应的行上
}

这样,每隔一会儿我们就会得到一只停在原地的僵尸。
接下来,我们会在每一帧遍历僵尸,让他们走动起来或者吃东西。

种太阳!

在这里插入图片描述

//
// 初始化太阳
//
function fallingSuns(){
	var fallingSunsTimer = setInterval(newSun, 3000);
}
//
// 增加一个新的阳光
//
function newSun(){
	var sunRow=Math.floor(Math.random()*5);// 随机行
	var sunCol=Math.floor(Math.random()*9);// 随机列
	var sun = new lib.sunMc();// 构造太阳
	sun.mouseChildren = false;//让整个太阳影片剪辑成为一个整体
	sun.buttonMode = true;// 当鼠标滑过阳光时,改变鼠标的形状
	sunContainer.addChild(sun);// 添加
	sun.x=borderLeft + sunCol*gridWidth;// 把太阳放在对应的列上
	sun.destinationY = borderTop+sunRow*gridHeight;// definines the sun y destination point
	sun.y=20;// 把阳光放在舞台顶部的上方
	sun.addEventListener("click",sunClicked);// 给阳光注册点击事件
}

注意sun.mouseChildren = false这句话,他很重要。
种太阳的逻辑和生成僵尸一样。
不同的是,我们要给太阳添加鼠标点击事件。

//
// 点击阳光
//
function sunClicked(event){
	var sunToRemove = event.target;//获得被点击的太阳
	sunToRemove.removeEventListener("click",sunClicked);// removes the CLICK listener
	money += 50;// makes the player earn money (5)
	updateMoney();// updates money text
	//console.log("sunClicked! ");
	sunContainer.removeChild(sunToRemove);// 移除
}

好的,现在我们每隔一段时间就会有一个不会动的太阳生成。
后面要在每帧中遍历太阳,让他们下落,或者逐渐消失。

种豌豆

种下植物的逻辑有些不一样,我们来捋一下。
首先在舞台上要有一个卡片让我们选择,并且这个卡片具有一个点击侦听。
点击后会执行onPlantClicked()。
在这里插入图片描述

//
// 创建一个植物栏,现在只有一种植物
//
function addPlants(){
	var card_peaShooter = new lib.peaShooter();// 构造一株新的植物
	overlayContainer.addChild(card_peaShooter);// 增加植物
	card_peaShooter.buttonMode=true;// 鼠标滑过改变形状
	card_peaShooter.x = 60;
	card_peaShooter.y = 60;
	card_peaShooter.addEventListener("click",onPlantClicked);// 植物选择区域注册点击事件
}

点击卡片后会发生什么事情呢?
在这里插入图片描述

//
// 选择植物卡片
//
function onPlantClicked(){
	// 检查玩家是否有足够的钱(当前是10)来购买植物,并且是否正在拖动一个植物
	if (money >= 100&&! playerMoving) {
		money -= 100;// pays the plant
		updateMoney();// updates money text
		selector = new lib.selectorMc();// 创建一个新的选择器
		selector.visible = false;// 使选择器不可见
		overlayContainer.addChild(selector);// 把选择器加入到显示列表
		movingPlant = new lib.plantMovingMc();// 构建一个供玩家拖动的植物
		movingPlant.addEventListener("click",placePlant);// 给拖动的植物注册点击事件
		overlayContainer.addChild(movingPlant);//把该植物加入到显示列表
		playerMoving = true;//告诉脚本正在移动一株植物
	}
}

好的,现在我们拥有一个卡片,点击后会创建一个可以拖动的植物,我们会在每帧执行的函数中让它的坐标等于我们鼠标的坐标。
我们给拖动中的植物也添加了点击侦听,点击后会发生什么?
在这里插入图片描述

//
// 放置植物
//
function placePlant(){
	
	var plantRow=Math.floor((stage.mouseY-borderTop)/gridHeight);
	var plantCol=Math.floor((stage.mouseX-borderLeft)/gridWidth);
	// 判断鼠标是否在放置区域内部并且上面没有植物
	if (plantRow>=0&&plantCol>=0&&plantRow<5&&plantCol<9&&plantsArray[plantRow][plantCol]==0) {
		var placedPlant=new lib.plantMc();// 构建一株植物,用来种植
		placedPlant.name="plant_"+plantRow+"_"+plantCol;// 给植物一个名字
		placedPlant.fireRate=48;//  植物的开火速率,单位帧
		placedPlant.recharge=48;// 当recharge 等于 fireRate时,植物已经准备好开火了
		placedPlant.plantRow=plantRow;// plant row
		plantContainer.addChild(placedPlant);// adds the plant
		//console.log("plantRow:"+plantRow);
		//console.log("plantCol:"+plantCol);
		placedPlant.x=plantCol*gridWidth+borderLeft+30;
		placedPlant.y=plantRow*gridHeight+borderTop+30;
		//console.log("plant's x:"+placedPlant.x);
		//console.log("plant's y:"+placedPlant.y);
		playerMoving=false;// tells the script the player is no longer moving
		movingPlant.removeEventListener("click",placePlant);// removes the CLICK listener from the draggable plant
		overlayContainer.removeChild(selector);// removes the selector
		overlayContainer.removeChild(movingPlant);// removes the plant itself
		plantsArray[plantRow][plantCol]=1;// 更新游戏区块信息
	}
}

仔细看,这段有一个bool变量叫做playerMoving,这个变量会告诉selector(植物影子)是否要判断自己出不出现。

下面的内容每帧都要执行!!!

下面的每一个代码片段,都被包含在onEnterFrm()函数里面。

selector(植物影子)和拖动中的植物

在这里插入图片描述
每一帧中,我们都判断玩家是否在移动植物。

    //
	// 植物放置
	//
	if (playerMoving) {
		movingPlant.x=stage.mouseX;
		movingPlant.y=stage.mouseY;
		//console.log("mouseY:"+stage.mouseY);
		var plantRow=Math.floor((stage.mouseY-borderTop)/gridHeight);
		var plantCol=Math.floor((stage.mouseX-borderLeft)/gridWidth);
		//console.log("plantRow:"+plantRow);
		//console.log("plantCol:"+plantCol);
		// 鼠标在区域内
		if (plantRow>=0&&plantCol>=0&&plantRow<5&&plantCol<9) {
			selector.visible = true;// shows the selector
			selector.x=borderLeft+plantCol*gridWidth+30;
			selector.y=borderTop+plantRow*gridHeight+30;
		} else {
			selector.visible=false;// hide the selector
		}
	}
让太阳动起来

先前我们创建了不会动的太阳,现在每一帧,我们遍历它,让它不做懒狗。
在这里插入图片描述

	//
	// 阳光管理
	//
	for (i=0; i<sunContainer.numChildren; i++) {
		var fallingSun = sunContainer.getChildAt(i);
		// 阳光下落
		if (fallingSun.y<fallingSun.destinationY) {
			fallingSun.y++;// 接着下落
		} else {
			fallingSun.alpha-=0.01;// 落完逐渐消失
			// 阳光消失了
			if (fallingSun.alpha<0) {
				fallingSun.removeEventListener("click",sunClicked);
				sunContainer.removeChild(fallingSun);// removes the sun
				//console.log("下面展现的是消失的太阳");
				//console.log(fallingSun);
			}
		}
	}
现在要让植物吐子弹了

在这里插入图片描述

	//
	// 植物管理
	//
	for (i = 0; i < plantContainer.numChildren; i++) {
		var currentPlant = plantContainer.getChildAt(i);//遍历每个植物
		// 准备好开火
		if (currentPlant.recharge == currentPlant.fireRate ) {
			// 检查是否有僵尸与植物处于同一行
			if (zombiesArray[currentPlant.plantRow].length > 0) {
				// 同行有僵尸
				for (j=0; j < zombiesArray[currentPlant.plantRow].length; j++) {//遍历当前行的僵尸
					var targetZombie = zombieContainer.getChildByName(zombiesArray[currentPlant.plantRow][j]);// 获取僵尸
					// 僵尸在右
					if (targetZombie.x > currentPlant.x) {
						var bullet = new lib.bulletMc();// 构造新子弹
						bulletContainer.addChild(bullet);// 添加子弹
						bullet.x = currentPlant.x+20;
						bullet.y = currentPlant.y;
						bullet.sonOf = currentPlant;//子弹当儿子,存储该子弹是由哪一株植物射出的
						currentPlant.recharge = 0;// 重新装填
						break;// exits the j for loop
					}
				}
			}
		}
		// 子弹装填中
		if (currentPlant.recharge < currentPlant.fireRate) {
			currentPlant.recharge ++;// recharges the plant
		}
	}

非常棒,现在我们的子弹容器储存了全部的子弹,并且我们可以通过bullet.sonOf属性得到子弹的主人。
接下来,我们就要遍历子弹,控制子弹移动和打人。

子弹打人

在这里插入图片描述
不难发现,我们需要内外两重循环,内重循环中我们还需要做判断。

	//
	// 子弹管理
	//
	for (i=0; i<bulletContainer.numChildren; i++) {
		var movingBullet = bulletContainer.getChildAt(i);
		movingBullet.x+=3;//子弹移动
		var firingPlant = movingBullet.sonOf;//获得这个子弹是哪个植物射击的
		// 子弹飞走了
		
		for (j=0; j<zombieContainer.numChildren; j++) {
			var movingZombie=zombieContainer.getChildAt(j);
			if (movingZombie.zombieRow != firingPlant.plantRow) continue;
			// 碰撞检测
			if (movingZombie.x < movingBullet.x && movingZombie.x > movingBullet.x-5 ){
				movingZombie.alpha-=0.3;// 僵尸逐渐死亡
				bulletContainer.removeChild(movingBullet);// 移除子弹
				// 僵尸死了
				if (movingZombie.alpha<0) {
					zombiesArray[movingZombie.zombieRow].splice(zombiesArray[movingZombie.zombieRow].indexOf(movingZombie.name),1);// 减少该行的僵尸 
					zombieContainer.removeChild(movingZombie);// 移除显示列表
				}
				break;
			}
		}
		if (movingBullet.x>1200) {
			bulletContainer.removeChild(movingBullet);// 移除这颗子弹
		}
	}

到目前为止,植物会发射子弹,并且子弹会打僵尸了。

僵尸吃菜

这个逻辑没有植物打人那么绕。
在这里插入图片描述

	//
	// 僵尸吃菜
	//
	var zombieColumn;
	for (i=0; i<zombieContainer.numChildren; i++) {//遍历僵尸
		movingZombie=zombieContainer.getChildAt(i);
		zombieColumn = Math.floor((movingZombie.x-borderLeft)/gridWidth);// 得到僵尸所在的列
		//console.log("zombieColumn"+zombieColumn);
		// 判断是否有植物在同一块中
		if (zombieColumn<0||zombieColumn>8||plantsArray[movingZombie.zombieRow][zombieColumn]==0) {
			movingZombie.x-=0.5;// moves each zombie left by 1/2 pixels
		} else {
			// 僵尸打人
			//console.log("僵尸吃菜中");
			var attackedPlant = plantContainer.getChildByName("plant_"+movingZombie.zombieRow+"_"+zombieColumn);
			attackedPlant.alpha-=0.01;// 植物逐渐死亡
			// 植物死了
			if (attackedPlant.alpha < 0) {
				plantsArray[movingZombie.zombieRow][zombieColumn] = 0;//removes the plant from the array
				plantContainer.removeChild(attackedPlant);//removes the plant Display Object from Display List
			}
		}
	}

好了!现在僵尸也学会了反抗!

代码总结

再贴一次思维导图,大家一定要对着这个来!
PVZ逻辑示意图
下面是全部代码:


//
// 二维数组游戏区块
//
var plantsArray = new Array()//种植下的植物数组
var zombiesArray = new Array();//出现的僵尸数组
//
//行高和列宽
//
var gridHeight = 115;
var gridWidth = 95;
var borderTop = 110;//bordertop
var borderLeft = 320;//borderleft
//
// 容器
//
var sunContainer = new cjs.Container();// 阳光容器
var plantContainer = new cjs.Container();// 植物容器
var bulletContainer = new cjs.Container();// 子弹容器
var zombieContainer = new cjs.Container();// 僵尸容器
var overlayContainer = new cjs.Container();// 覆盖物容器
//
// actors
//
var movingPlant;// 拖动中的植物
var selector;// 选择器
//
// other variables
//
var money = 100;// 金钱总量
var moneyText = new cjs.Text("Money:"+money,"50px Times","#000000")  ;// 动态文本框,用来显示玩家的金钱
var playerMoving = false;// 标志玩家是否在移动一个植物
var totalZombies = 0;


//
// 游戏区域设置,创建用来存储植物和僵尸信息的数组
//
function setupField(){

	for (var i = 0; i < 5; i++) {
		plantsArray[i]=new Array();
		zombiesArray[i]=new Array();
		for (var j=0; j<9; j++) {
			plantsArray[i][j]=0;
		}
	}
}

//
// 画出游戏区域
//
function drawField(){
	stage.addChild(sunContainer);
	stage.addChild(plantContainer);
	stage.addChild(bulletContainer);
	stage.addChild(zombieContainer);
	stage.addChild(overlayContainer);
	//Container前不能加stage.
	overlayContainer.addChild(moneyText);
	updateMoney();
	moneyText.textColor=0x000000;
	moneyText.height=20;
}

//
// 更新显示阳光值
//
function updateMoney(){
	moneyText.text = money.toString();
}
//
// 初始化僵尸
//
function addZombies(){
	var zombieTimer = setInterval(newZombie, 3000);
}

//
// 增加一个新的僵尸
//
function newZombie(TimerEvent){
	var zombie = new lib.zombieMc();// 构造僵尸
	totalZombies++;
	zombieContainer.addChild(zombie);// 添加僵尸
	zombie.zombieRow=Math.floor(Math.random()*5);// 随机行数
	zombie.name="zombie_"+totalZombies;//僵尸取名
	zombiesArray[zombie.zombieRow].push(zombie.name);// 增加第row行的僵尸
	zombie.x=1000;// 把僵尸放在屏幕的右边
	zombie.y=zombie.zombieRow*gridHeight+borderTop;//把僵尸放到对应的行上
}
//
// 初始化太阳
//
function fallingSuns(){
	var fallingSunsTimer = setInterval(newSun, 3000);
}
//
// 增加一个新的阳光
//
function newSun(){
	var sunRow=Math.floor(Math.random()*5);// 随机行
	var sunCol=Math.floor(Math.random()*9);// 随机列
	var sun = new lib.sunMc();// 构造太阳
	sun.mouseChildren = false;//让整个太阳影片剪辑成为一个整体
	sun.buttonMode = true;// 当鼠标滑过阳光时,改变鼠标的形状
	sunContainer.addChild(sun);// 添加
	sun.x=borderLeft + sunCol*gridWidth;// 把太阳放在对应的列上
	sun.destinationY = borderTop+sunRow*gridHeight;// definines the sun y destination point
	sun.y=20;// 把阳光放在舞台顶部的上方
	sun.addEventListener("click",sunClicked);// 给阳光注册点击事件
}
//
// 点击阳光
//
function sunClicked(event){
	var sunToRemove = event.target;//获得被点击的太阳
	sunToRemove.removeEventListener("click",sunClicked);// removes the CLICK listener
	money += 50;// makes the player earn money (5)
	updateMoney();// updates money text
	//console.log("sunClicked! ");
	sunContainer.removeChild(sunToRemove);// 移除
}
//
// 创建一个植物栏,现在只有一种植物
//
function addPlants(){
	var card_peaShooter = new lib.peaShooter();// 构造一株新的植物
	overlayContainer.addChild(card_peaShooter);// 增加植物
	card_peaShooter.buttonMode=true;// 鼠标滑过改变形状
	card_peaShooter.x=60;
	card_peaShooter.y=60;
	card_peaShooter.addEventListener("click",onPlantClicked);// 植物选择区域注册点击事件
}

//
// 选择植物卡片
//
function onPlantClicked(){
	// 检查玩家是否有足够的钱(当前是10)来购买植物,并且是否正在拖动一个植物
	if (money>=100&&! playerMoving) {
		money-=100;// pays the plant
		updateMoney();// updates money text
		selector = new lib.selectorMc();// 创建一个新的选择器
		selector.visible=false;// 使选择器不可见
		console.log("创建了一个selector!"+selector);
		overlayContainer.addChild(selector);// 把选择器加入到显示列表
		movingPlant=new lib.plantMovingMc();// 构建一个供玩家拖动的植物
		movingPlant.addEventListener("click",placePlant);// 给拖动的植物注册点击事件
		overlayContainer.addChild(movingPlant);//把该植物加入到显示列表
		playerMoving=true;//告诉脚本正在移动一株植物
	}
}
//
// 放置植物
//
function placePlant(){
	
	var plantRow=Math.floor((stage.mouseY-borderTop)/gridHeight);
	var plantCol=Math.floor((stage.mouseX-borderLeft)/gridWidth);
	// 判断鼠标是否在放置区域内部并且上面没有植物
	if (plantRow>=0&&plantCol>=0&&plantRow<5&&plantCol<9&&plantsArray[plantRow][plantCol]==0) {
		var placedPlant=new lib.plantMc();// 构建一株植物,用来种植
		placedPlant.name="plant_"+plantRow+"_"+plantCol;// 给植物一个名字
		placedPlant.fireRate=48;//  植物的开火速率,单位帧
		placedPlant.recharge=48;// 当recharge 等于 fireRate时,植物已经准备好开火了
		placedPlant.plantRow=plantRow;// plant row
		plantContainer.addChild(placedPlant);// adds the plant
		//console.log("plantRow:"+plantRow);
		//console.log("plantCol:"+plantCol);
		placedPlant.x=plantCol*gridWidth+borderLeft+30;
		placedPlant.y=plantRow*gridHeight+borderTop+30;
		//console.log("plant's x:"+placedPlant.x);
		//console.log("plant's y:"+placedPlant.y);
		playerMoving=false;// tells the script the player is no longer moving
		movingPlant.removeEventListener("click",placePlant);// removes the CLICK listener from the draggable plant
		overlayContainer.removeChild(selector);// removes the selector
		overlayContainer.removeChild(movingPlant);// removes the plant itself
		plantsArray[plantRow][plantCol]=1;// 更新游戏区块信息
	}
}
//
// core function to be executed at every frame. The whole game is managed here
//
function onEnterFrm(){
	var i;
	var j;
	var k;//这些变量都是用来遍历的
	
	//
	// 植物管理
	//
	for (i = 0; i < plantContainer.numChildren; i++) {
		var currentPlant = plantContainer.getChildAt(i);//遍历每个植物
		// 准备好开火
		if (currentPlant.recharge == currentPlant.fireRate ) {
			// 检查是否有僵尸与植物处于同一行
			if (zombiesArray[currentPlant.plantRow].length > 0) {
				// 同行有僵尸
				for (j=0; j < zombiesArray[currentPlant.plantRow].length; j++) {//遍历当前行的僵尸
					var targetZombie = zombieContainer.getChildByName(zombiesArray[currentPlant.plantRow][j]);// 获取第j个僵尸
					// 僵尸在右
					if (targetZombie.x > currentPlant.x) {
						var bullet = new lib.bulletMc();// 构造新子弹
						bulletContainer.addChild(bullet);// 添加子弹
						bullet.x = currentPlant.x+20;
						bullet.y = currentPlant.y-10;
						bullet.sonOf = currentPlant;//子弹当儿子,存储该子弹是由哪一株植物射出的
						currentPlant.recharge = 0;// 重新装填
						break;// exits the j for loop
					}
				}
			}
		}
		// 子弹装填中
		if (currentPlant.recharge < currentPlant.fireRate) {
			currentPlant.recharge ++;// recharges the plant
		}
	}
	//
	// 子弹管理
	//
	for (i=0; i<bulletContainer.numChildren; i++) {
		var movingBullet = bulletContainer.getChildAt(i);
		movingBullet.x+=3;//子弹移动
		var firingPlant = movingBullet.sonOf;//获得这个子弹是哪个植物射击的
		// 子弹飞走了
		
		for (j=0; j<zombieContainer.numChildren; j++) {
			var movingZombie=zombieContainer.getChildAt(j);
			//对于当前子弹,遍历的这只僵尸如果不在同一行,就继续找下一只僵尸
			if (movingZombie.zombieRow != firingPlant.plantRow) continue;
			// 碰撞检测
			if (movingZombie.x < movingBullet.x && movingZombie.x > movingBullet.x-5 ){
				movingZombie.alpha-=0.3;// 僵尸逐渐死亡
				bulletContainer.removeChild(movingBullet);// 移除子弹
				// 僵尸死了
				if (movingZombie.alpha<0) {
					zombiesArray[movingZombie.zombieRow].splice(zombiesArray[movingZombie.zombieRow].indexOf(movingZombie.name),1);// 减少该行的僵尸 
					zombieContainer.removeChild(movingZombie);// 移除显示列表
				}
				break;
			}
		}
		if (movingBullet.x>1200) {
			bulletContainer.removeChild(movingBullet);// 移除这颗子弹
		}
	}
	//
	// 僵尸吃菜
	//
	var zombieColumn;
	for (i=0; i<zombieContainer.numChildren; i++) {//遍历僵尸
		movingZombie=zombieContainer.getChildAt(i);
		zombieColumn = Math.floor((movingZombie.x-borderLeft)/gridWidth);// 得到僵尸所在的列
		//console.log("zombieColumn"+zombieColumn);
		// 判断是否有植物在同一块中
		if (zombieColumn<0||zombieColumn>8||plantsArray[movingZombie.zombieRow][zombieColumn]==0) {
			movingZombie.x-=0.5;// moves each zombie left by 1/2 pixels
		} else {
			// 僵尸打人
			//console.log("僵尸吃菜中");
			var attackedPlant = plantContainer.getChildByName("plant_"+movingZombie.zombieRow+"_"+zombieColumn);
			attackedPlant.alpha-=0.01;// 植物逐渐死亡
			// 植物死了
			if (attackedPlant.alpha < 0) {
				plantsArray[movingZombie.zombieRow][zombieColumn] = 0;//removes the plant from the array
				plantContainer.removeChild(attackedPlant);//removes the plant Display Object from Display List
			}
		}
		
	}
	//
	// 阳光管理
	//
	for (i=0; i<sunContainer.numChildren; i++) {
		var fallingSun = sunContainer.getChildAt(i);
		// 阳光下落
		if (fallingSun.y<fallingSun.destinationY) {
			fallingSun.y++;// 接着下落
		} else {
			fallingSun.alpha-=0.01;// 落完逐渐消失
			// 阳光消失了
			if (fallingSun.alpha<0) {
				fallingSun.removeEventListener("click",sunClicked);
				sunContainer.removeChild(fallingSun);// removes the sun
				//console.log("下面展现的是消失的太阳");
				//console.log(fallingSun);
			}
		}
	}
	//
	// 植物放置
	//
	if (playerMoving) {
		movingPlant.x=stage.mouseX;
		movingPlant.y=stage.mouseY;
		//console.log("mouseY:"+stage.mouseY);
		var plantRow=Math.floor((stage.mouseY-borderTop)/gridHeight);
		var plantCol=Math.floor((stage.mouseX-borderLeft)/gridWidth);
		//console.log("plantRow:"+plantRow);
		//console.log("plantCol:"+plantCol);
		// 鼠标在区域内
		if (plantRow>=0&&plantCol>=0&&plantRow<5&&plantCol<9) {
			selector.visible = true;// shows the selector
			selector.x=borderLeft+plantCol*gridWidth+30;
			selector.y=borderTop+plantRow*gridHeight+30;
		} else {
			selector.visible=false;// hide the selector
		}
	}
}


main();
function main(){
	setupField();
	drawField();
	addZombies();
	addPlants();
	fallingSuns();
	stage.addEventListener("tick",onEnterFrm);
	
}

/////////////////////////////////////////////////////////////
//有一些地方请大家自行探索,如有不会,请在群里讨论
//
//--图层显示遮挡的问题,请结合课本第三章拖动彩色方块的那个游戏,设置不同对象处于不同图层。
//--音乐。请结合实验报告,总结音乐的产生与消失。
//--对象的mouseChildren属性,这个属性究竟控制了什么内容。
//--还有关于分辨率的问题。在网页不同的缩放状态下,鼠标的位置在脚本中会有一定倍率的映射,简言之就是对不准。。此问题详见更新




好了!大致就是这样了!

最后BB两句

希望大家搞懂整个逻辑,在这个教程的基础上进行一定的创新拓展!
只需要稍稍动手,就可以完成很多有趣的东西!
不妨自己写一种植物的攻击逻辑、或者是一种僵尸的反抗逻辑
或者按照学长的实验报告写出铲子和太阳的功能
对教程中不清楚的地方请联系我。
有其他问题请找仕爷,他的咖啡味道不错的!
最后贴一下成品。
植物大战僵尸_演示demo
每个对象的小红点表示这个对象自身坐标系的原点。

更新日志

2019.11.13

关于网页缩放后鼠标位移和movingPlant对不准的问题:
问题描述:

在这里插入图片描述
以上两张图为网页在100%缩放下,鼠标在不同位置时,movingPlant和鼠标的相对位置。并没有出现异常。

在这里插入图片描述
在这里插入图片描述
后面两张是网页缩放120%时的样子,可以看见movingPlant和鼠标位置产生了偏移。
产生偏移时,我们的鼠标始终点击不到移动的植物,所以我们也就种不下豌豆。

产生原因:
这是因为在网页缩放时,只放大了我们所看到的内容。在网页的html文件代码中,我们会发现一个canvasResize()函数,它是用来缩放canvas的大小的。而在An中构建文件时,我选用的时1280*720,它并不会随着网页缩放而缩放。

  • 脚本读取的是我们的鼠标在stage上的位置,即stage.mouseX最大值为1280。
  • 试想一下,当我们的stage.mouseX最大时,我们的movingPlant应该在何处?在canvas的最右边。
  • 然而事实上,在网页放大时,我们的鼠标仅仅是在canvas的中间位置。
  • 所以,这就是为什么越往右下角走,两者的偏移量越大。

解决办法
在这里插入图片描述
在整个脚本中,** 所有 **用到stage.mouseX和stage.mouseY的地方,都让他们除以舞台的缩放数值。
如图。
再次尝试我们的demo,完美!无论我怎么缩放,movingPlant都粘着我的鼠标~

发布了14 篇原创文章 · 获赞 26 · 访问量 3293

猜你喜欢

转载自blog.csdn.net/weixin_44338553/article/details/102998704
今日推荐