让我们先来明确一下什么时候植能开火:
*当至少有一个僵尸与植物处于同一行
*植物一次只能射出一颗子弹
*距离上一次射击必须过去一定的时间
现在让我们来定义一下子弹的活动:
*子弹从左往右飞
*当子弹击中一个僵尸时被移除
*当子弹飞到舞台之外时被移除
这6个概念给我们的脚本带来了一些大的改动。意识到把所有的代码写到一个类里使得脚本变得很混乱,我尽力以最清晰的方式来组织它。我尽我最大所能来使得它保持可读性。
我创建了一个叫做bulletMc的对象,它代表了子弹。
准备好去看一个接近300行的代码吗?
package { import flash.display.Sprite; import flash.utils.Timer; import flash.events.TimerEvent; import flash.events.MouseEvent; import flash.events.Event; import flash.text.TextField; public class Main extends Sprite { //一个2维数组用来存储游戏区块 private var plantsArray:Array;// 种植在游戏区域里的植物 private var zombiesArray:Array;//在游戏区域里的僵尸 // // 计时器 // private var flowersTimer:Timer=new Timer(5000);//计时器,使得阳光落下 private var zombieTimer:Timer=new Timer(5000);//计时器,让僵尸出场 // // 容器 // private var sunContainer:Sprite=new Sprite();// 所有阳光的容器 private var plantContainer:Sprite=new Sprite();// 所有植物的容器 public var bulletContainer:Sprite=new Sprite();// 所有子弹的容器 private var zombieContainer:Sprite=new Sprite();// 所有僵尸的容器 private var overlayContainer:Sprite=new Sprite();// 所有翻盖物的容器 // // 我们的演员 // private var movingPlant:plantMc;// 玩家在游戏区域能够拖动的植物 private var selector:selectorMc;// 选择器(一个高亮的区块),告诉玩家他将把植物种在哪 // // 其它变量 // private var money:uint=0;// 玩家所拥有的金钱数量 private var moneyText:TextField=new TextField ;// 动态文本框,用来显示玩家的金钱 private var playerMoving:Boolean=false;// 布尔型变量,标志玩家是否在移动一个植物 public function Main():void { setupField();// 初始化游戏区块 drawField();// 画出游戏区块 fallingSuns();// 初始化下落的阳光 addPlants();// 初始化植物 addZombies();// 初始化僵尸 addEventListener(Event.ENTER_FRAME,onEnterFrm); } // // 游戏区域设置,创建用来存储植物和僵尸信息的数组 // private function setupField():void { plantsArray=new Array(); for (var i:uint=0; i<5; i++) { plantsArray[i]=new Array(); for (var j:uint=0; j<9; j++) { plantsArray[i][j]=0; } } zombiesArray=new Array(0,0,0,0,0); } // // 显示玩家的金钱 // private function updateMoney():void { moneyText.text="Money: "+money.toString(); } // // 画出游戏区域 // private function drawField():void { var fieldSprite:Sprite=new Sprite(); var randomGreen:Number; addChild(fieldSprite); fieldSprite.graphics.lineStyle(1,0xFFFFFF); for (var i:uint=0; i<5; i++) { for (var j:uint=0; j<9; j++) { randomGreen=(125+Math.floor(Math.random()*50))*256; fieldSprite.graphics.beginFill(randomGreen); fieldSprite.graphics.drawRect(25+65*j,80+75*i,65,75); } } addChild(sunContainer); addChild(plantContainer); addChild(bulletContainer); addChild(zombieContainer); addChild(overlayContainer); overlayContainer.addChild(moneyText); updateMoney(); moneyText.textColor=0xFFFFFF; moneyText.height=20; } // // 初始化僵尸 // private function addZombies():void { zombieTimer.start(); zombieTimer.addEventListener(TimerEvent.TIMER,newZombie); } // // 增加一个新的僵尸 // private function newZombie(e:TimerEvent):void { var zombie:zombieMc=new zombieMc();// 构造僵尸 zombieContainer.addChild(zombie);// 增加僵尸 zombie.zombieRow=Math.floor(Math.random()*5);// 生成随机行数,用于放置僵尸 zombiesArray[zombie.zombieRow]++;// 增加第row行的僵尸数量 zombie.x=660;// 把僵尸放在屏幕的右边 zombie.y=zombie.zombieRow*75+115; } // // 初始化阳光 // private function fallingSuns():void { flowersTimer.start(); flowersTimer.addEventListener(TimerEvent.TIMER, newSun); } // // 增加一束新的阳光 // private function newSun(e:TimerEvent):void { var sunRow:uint=Math.floor(Math.random()*5);// 随机行 var sunCol:uint=Math.floor(Math.random()*9);// 随机列 var sun:sunMc = new sunMc();// 构造阳光 sun.buttonMode=true;// 当鼠标滑过阳光时,改变鼠标的形状 sunContainer.addChild(sun);// 增加阳光 sun.x=52+sunCol*65;// 把阳光放在合适的位置 sun.destinationY=130+sunRow*75;// 定义阳光destinationY属性 sun.y=-20;// 把阳光放在舞台顶部的上方 sun.addEventListener(MouseEvent.CLICK,sunClicked);// 给阳光注册鼠标点击事件 } // // 阳光的鼠标点击事件句柄 // private function sunClicked(e:MouseEvent):void { e.currentTarget.removeEventListener(MouseEvent.CLICK,sunClicked);// 移除鼠标事件侦听 money+=5;//让玩家赚到5个金币 updateMoney();// 更新动态文本 var sunToRemove:sunMc=e.currentTarget as sunMc;// 获得我们必须移除的阳光 sunContainer.removeChild(sunToRemove);// 移除该阳光 } // // 创建一个植物栏,现在只有一种植物 // private function addPlants():void { var plant:plantMc=new plantMc();// 构造一株新的植物 overlayContainer.addChild(plant);// 增加植物 plant.buttonMode=true;// 使鼠标改变形状,当它滑过新植物时 plant.x=90; plant.y=40; plant.addEventListener(MouseEvent.CLICK,onPlantClicked);// 给新植物注册鼠标点击事件 } // // 植物的鼠标点击事件句柄 // private function onPlantClicked(e:MouseEvent):void { // 检查玩家是否有足够的钱(当前是10)来购买植物,并且是否正在拖动一个植物 if (money>=10&&! playerMoving) { money-=10;// 付款 updateMoney();// 更新动态文本 selector=new selectorMc();// 创建一个新的选择器 selector.visible=false;// 使选择器不可见 overlayContainer.addChild(selector);// 把选择器加入到显示列表 movingPlant=new plantMc();// 构建一个新的供玩家拖动的植物 movingPlant.addEventListener(MouseEvent.CLICK,placePlant);// 给该植物注册一个鼠标点击事件 overlayContainer.addChild(movingPlant);// 把该植物加入到显示列表 playerMoving=true;// 告诉脚本正在移动一株植物 } } // // 把植物放置在游戏区域中 // private function placePlant(e:MouseEvent):void { var plantRow:int=Math.floor((mouseY-80)/75); var plantCol:int=Math.floor((mouseX-25)/65); // 检查该区块是否位于游戏区域内,并且该区块没有其它植物存在 if (plantRow>=0&&plantCol>=0&&plantRow<5&&plantCol<9&&plantsArray[plantRow][plantCol]==0) { var placedPlant:plantMc=new plantMc();// 构建一株植物,用来种植 placedPlant.fireRate=75;// 植物的开火速率,单位帧 placedPlant.recharge=0;// 当recharge 等于 fireRate时,植物已经准备好开火了 placedPlant.isFiring=false;// 一个布尔变量来存储植物是否正在开火 placedPlant.plantRow=plantRow;// 植物所在的行 plantContainer.addChild(placedPlant);// 把该植物加入到显示列表 placedPlant.x=plantCol*65+57; placedPlant.y=plantRow*75+115; playerMoving=false;// 告诉脚本玩家不在移动植物了 movingPlant.removeEventListener(MouseEvent.CLICK,placePlant);// 移除事件侦听 overlayContainer.removeChild(selector);// 移除选择器 overlayContainer.removeChild(movingPlant);// 移除供拖动的植物 plantsArray[plantRow][plantCol]=1;// 更新游戏区块信息 } } // // 游戏循环,游戏的核心函数 // private function onEnterFrm(e:Event):void { var i:int; var j:int; // // 植物管理 // for (i=0; i<plantContainer.numChildren; i++) { var currentPlant:plantMc=plantContainer.getChildAt(i) as plantMc; // 让我们看看植物是否能开火 if (zombiesArray[currentPlant.plantRow]>0&¤tPlant.recharge==currentPlant.fireRate&&! currentPlant.isFiring) { var bullet:bulletMc=new bulletMc();// 构造一颗新的子弹 bulletContainer.addChild(bullet);// 把子弹加入到显示列表 bullet.x=currentPlant.x; bullet.y=currentPlant.y; bullet.sonOf=currentPlant;// 存储该子弹是由哪一株植物射出的 currentPlant.recharge=0;// 重新准备开火 currentPlant.isFiring=true;// 植物正在开火 } if (currentPlant.recharge<currentPlant.fireRate) { currentPlant.recharge++; } } // // 子弹管理 // for (i=0; i<bulletContainer.numChildren; i++) { var movingBullet:bulletMc=bulletContainer.getChildAt(i) as bulletMc; movingBullet.x+=3;//把每个子弹向右移动3个像素 var firingPlant:plantMc=movingBullet.sonOf as plantMc;// 获得这个子弹是哪个植物射击的 // 让我们看看子弹是否飞出了舞台 if (movingBullet.x>650) { firingPlant.isFiring=false;// 植物不再处于正在开火的状态 bulletContainer.removeChild(movingBullet);// 移除子弹 } else { for (j=0; j<zombieContainer.numChildren; j++) { var movingZombie:zombieMc=zombieContainer.getChildAt(j) as zombieMc; // 让我们看看植物是否被子弹击中 if (movingZombie.hitTestPoint(movingBullet.x,movingBullet.y,true)) { movingZombie.alpha-=0.3;// 减少僵尸的能量(透明度) firingPlant.isFiring=false;// 植物不再处于正在开火的状态 bulletContainer.removeChild(movingBullet);// 移除子弹 // 让我们看看僵尸的能量(透明度)是否降至为0了 if (movingZombie.alpha<0) { zombiesArray[movingZombie.zombieRow]--;// 减少该行僵尸的数量 zombieContainer.removeChild(movingZombie);// 移除僵尸 } break; } } } } // // 僵尸管理 // for (i=0; i<zombieContainer.numChildren; i++) { movingZombie=zombieContainer.getChildAt(i) as zombieMc; movingZombie.x-=0.5;// 每一个僵尸往左移动0.5个像素 } // // 阳光管理 // for (i=0; i<sunContainer.numChildren; i++) { var fallingSun:sunMc=sunContainer.getChildAt(i) as sunMc; // 让我们看看阳光是否还在下落 if (fallingSun.y<fallingSun.destinationY) { fallingSun.y++;// 把阳光往下移动一个像素 } else { fallingSun.alpha-=0.01;// 使阳光淡出 // 检查阳光是否消失了 if (fallingSun.alpha<0) { fallingSun.removeEventListener(MouseEvent.CLICK,sunClicked);// 移除事件侦听 sunContainer.removeChild(fallingSun);// 移出显示列表 } } } // // 安置植物 // if (playerMoving) { movingPlant.x=mouseX; movingPlant.y=mouseY; var plantRow:int=Math.floor((mouseY-80)/75); var plantCol:int=Math.floor((mouseX-25)/65); // 检查是否在游戏区域内 if (plantRow>=0&&plantCol>=0&&plantRow<5&&plantCol<9) { selector.visible=true;// 显示选择器 selector.x=25+plantCol*65; selector.y=80+plantRow*75; } else { selector.visible=false;//隐藏选择器 } } } } }
如果你一路跟随着我之前的几个教程做了下来的说话,你就会注意到我改变了很多的代码,但是原来的概念保持不变。
我会把注意力放到新的东西上。首先,创建了一个新的数组,名为zombiesArray(第18行),它会存储每一行上的僵尸。这是非常有用的当我们想要知道一株植物是否能开火时。
此刻,只要有一个僵尸与植物处于同一行时植物就能射击,我并不关心僵尸是在植物的左边还是右边,但是这个地方我将会再下一个步骤来改变它,使得只有僵尸在植物
的右边时,植物才能开火,因为我可不想把植物放在僵尸的右边,然后看着它往右没有目标地开火。
当一个新的僵尸被加到舞台,看一下newZombie函数的这两行:
zombie.zombieRow=Math.floor(Math.random()*5);
zombiesArray[zombie.zombieRow]++;
给僵尸声明一个zombieRow属性,然后增加zombiesArray的第zombieRow个元素的值。多亏了这个数组,使得我总是可以轻易地知道有每一行分别有多少僵尸。
再看下placePlant函数(第170行),这个函数我用来放置一株植物,我将给植物增加一些属性:
placedPlant.fireRate=75;
placedPlant.recharge=0;
placedPlant.isFiring=false;
placedPlant.plantRow=plantRow;
我定义了植物开发的速率,单位是帧。它意味着植物至少要每隔fireRate帧开火一次。我选择用帧而不是用毫秒来作为植物开火的速率是因为如果有太多的对象在舞台上
,游戏循环速率就会变慢,而不是保持不变。recharge属性每秒都帧都会增加,当它等于fireRate时,植物就准备好射击了。isFiring属性告诉我们植物是否正在射击。
plantRow属性存储自己所在的行数。当检查有多少僵尸处于这一行时这个属性是很有用的。
来看一下是否一株植物可以开火,我们用这个语句来描述:
if (zombiesArray[currentPlant.plantRow]>0&¤tPlant.recharge==currentPlant.fireRate&&! currentPlant.isFiring) {
var bullet:bulletMc=new bulletMc();
bulletContainer.addChild(bullet);
bullet.x=currentPlant.x;
bullet.y=currentPlant.y;
bullet.sonOf=currentPlant;
currentPlant.recharge=0;
currentPlant.isFiring=true;
}
这个if语句检查与植物是否至少有一个僵尸与植物同一行,并且植物是否正在开火,是否准备好开火了。如果满足这些条件了,一个新的子弹就被创造出来了,并且植物
的一些状态就被改变了,来告诉脚本植物正在开火,并且需要重新准备。但是所有上面的这些功能的实现,我们需要给子弹一个sonOf属性来存储它是哪一株植物射出来的
。这是非常有用的一旦子弹要被移出舞台,我们就要更新射出该子弹的植物的isFiring状态。
最后看一下杀死僵尸部分的核心代码,它是一个for循环:
for (j=0; j<zombieContainer.numChildren; j++) {
var movingZombie:zombieMc=zombieContainer.getChildAt(j) as zombieMc;
if (movingZombie.hitTestPoint(movingBullet.x,movingBullet.y,true)) {
movingZombie.alpha-=0.3;
firingPlant.isFiring=false;
bulletContainer.removeChild(movingBullet);
if (movingZombie.alpha<0) {
zombiesArray[movingZombie.zombieRow]--;
zombieContainer.removeChild(movingZombie);
}
break;
}
}
我遍历所有的僵尸,进行碰撞检测。如果一个僵尸被子弹击中,我们移除子弹,并设置射出该子弹的植物的isFiring为false,并且减少僵尸的能量。此刻通过降低僵尸的
透明度。如果透明度降至0,我们移除该僵尸,并且减少zombiesArray相应位置元素的值来更新在该行上僵尸的数量。
现在你可以测试一下游戏了:
http://www.emanueleferonato.com/wp-content/uploads/2011/01/pvz4.swf