Do you like my tutorials?

Then consider supporting me on Ko-fi

Talking about Plants Vs Zombies game, Actionscript 3, Flash and Game development.

After a little pause, here we go with the 5th step of the tutorial.

In this step we’ll fix a bug which allowed plants to fire even if there isn’t any enemy coming from the right side, when there’s at least one enemy on the left side, and we’ll see zombies attacking the plants.

First, let me explain some changes: during previous steps, zombiesArray array was used only to count the number of zombies walking in each row. This information isn’t enough for us to know whether the zombies are on the left or on the right side of each plant, so from now on zombiesArray is an array of arrays filled with the names of the zombies walking in each row.

You’ll understand this feature better when we’ll discuss the source code.

To make zombies attack plants, we must stop them once they are on the same tile the plant is placed on.

Let’s see the source code:

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 {
		//
		// arrays to store game field information
		//
		private var plantsArray:Array;// plants placed in the game field
		private var zombiesArray:Array;//zombies placed in the game field
		// 
		// timers
		//
		private var flowersTimer:Timer=new Timer(5000);// timer to make flowers fall down
		private var zombieTimer:Timer=new Timer(5000);// timer to make zombies come in
		//
		// containers
		//
		private var sunContainer:Sprite=new Sprite();// container for all suns
		private var plantContainer:Sprite=new Sprite();// container for all plants
		public var bulletContainer:Sprite=new Sprite();// container for all bullets
		private var zombieContainer:Sprite=new Sprite();// container for all zombies
		private var overlayContainer:Sprite=new Sprite();// container of all overlays
		//
		// actors
		//
		private var movingPlant:plantMc;// plant the player can drag on game field
		private var selector:selectorMc;// the selector square to show the playere where he's going to place the plant
		//
		// other variables
		//
		private var money:uint=0;// amout of money owned by the player
		private var moneyText:TextField=new TextField  ;// dynamic text field where to show player's money
		private var playerMoving:Boolean=false;// Boolean variable to tell us if the player is moving a plant (true) or not (false)
		private var totalZombies:uint=0;//total amount of zombies placed in game
		public function Main():void {
			setupField();// initializes the game
			drawField();// draws the game field
			fallingSuns();// initializes the falling suns
			addPlants();// initialized the plants
			addZombies();// initializes the zombies
			addEventListener(Event.ENTER_FRAME,onEnterFrm);
		}
		//
		// game field setup. arrays to store plants and zombies information are created
		//
		private function setupField():void {
			plantsArray=new Array();
			zombiesArray=new Array();
			for (var i:uint=0; i<5; i++) {
				plantsArray[i]=new Array();
				zombiesArray[i]=new Array();
				for (var j:uint=0; j<9; j++) {
					plantsArray[i][j]=0;
				}
			}
		}
		//
		// showing the amount of money
		//
		private function updateMoney():void {
			moneyText.text="Money: "+money.toString();
		}
		//
		// game field drawing and children hierarchy management
		//
		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;
		}
		//
		// zombies initialization
		//
		private function addZombies():void {
			zombieTimer.start();
			zombieTimer.addEventListener(TimerEvent.TIMER,newZombie);
		}
		//
		// adding a new zombie
		//
		private function newZombie(e:TimerEvent):void {
			var zombie:zombieMc=new zombieMc();// constructs the zombie
			totalZombies++;
			zombieContainer.addChild(zombie);// adds the zombie
			zombie.zombieRow=Math.floor(Math.random()*5);// chooses a random row where to place the zombie
			zombie.name="zombie_"+totalZombies;//gives a name to the zombie
			zombiesArray[zombie.zombieRow].push(zombie.name);// adds the zombie in the row-th row
			zombie.x=660;// places the zombie on the board, outside the stage to the right
			zombie.y=zombie.zombieRow*75+115;
		}
		//
		// suns initialization
		//
		private function fallingSuns():void {
			flowersTimer.start();
			flowersTimer.addEventListener(TimerEvent.TIMER, newSun);
		}
		//
		// adding a new sun
		//
		private function newSun(e:TimerEvent):void {
			var sunRow:uint=Math.floor(Math.random()*5);// random row
			var sunCol:uint=Math.floor(Math.random()*9);// random column
			var sun:sunMc = new sunMc();// constructs the sun
			sun.buttonMode=true;// makes the mouse change shape when over the plant
			sunContainer.addChild(sun);// adds the sun
			sun.x=52+sunCol*65;// places the sun in the proper column
			sun.destinationY=130+sunRow*75;// definines the sun y destination point
			sun.y=-20;// places the sun out to the upper side of the stage
			sun.addEventListener(MouseEvent.CLICK,sunClicked);// listener to be triggered when the sun is clicked
		}
		//
		// handling clicks on suns
		//
		private function sunClicked(e:MouseEvent):void {
			e.currentTarget.removeEventListener(MouseEvent.CLICK,sunClicked);// removes the CLICK listener
			money+=5;// makes the player earn money (5)
			updateMoney();// updates money text
			var sunToRemove:sunMc=e.currentTarget as sunMc;// defines which sun we need to remove
			sunContainer.removeChild(sunToRemove);// removes the sun
		}
		//
		// building the plant toolbar (only 1 plant at the moment)
		//
		private function addPlants():void {
			var plant:plantMc=new plantMc();// constructs a new plant
			overlayContainer.addChild(plant);// adds the plant
			plant.buttonMode=true;// makes the mouse change shape when over the plant
			plant.x=90;
			plant.y=40;
			plant.addEventListener(MouseEvent.CLICK,onPlantClicked);// listener to be triggered once the plant is clicked
		}
		//
		// handling clicks on plants
		//
		private function onPlantClicked(e:MouseEvent):void {
			// let's see if the player has enough money (10) to afford the plant and isn't currently dragging a plant
			if (money>=10&&! playerMoving) {
				money-=10;// pays the plant
				updateMoney();// updates money text
				selector=new selectorMc();// constructs a new selector
				selector.visible=false;// makes the selector invisible
				overlayContainer.addChild(selector);// adds the selector
				movingPlant=new plantMc();// constructs a new moving plant
				movingPlant.addEventListener(MouseEvent.CLICK,placePlant);// lister to be triggered once the moving plant is clicked
				overlayContainer.addChild(movingPlant);// adds the moving plant
				playerMoving=true;// tells the script the player is actually moving a plant
			}
		}
		//
		// placing the plant on the game field
		//
		private function placePlant(e:MouseEvent):void {
			var plantRow:int=Math.floor((mouseY-80)/75);
			var plantCol:int=Math.floor((mouseX-25)/65);
			// let's see if the tile is inside the game field and it's free
			if (plantRow>=0&&plantCol>=0&&plantRow<5&&plantCol<9&&plantsArray[plantRow][plantCol]==0) {
				var placedPlant:plantMc=new plantMc();// constructs the plant to be placed
				placedPlant.name="plant_"+plantRow+"_"+plantCol;// gives the plant a name
				placedPlant.fireRate=75;// plant fire rate, in frames
				placedPlant.recharge=0;// plant recharge. When recharge is equal to fireRate, the plant is ready to fire
				placedPlant.isFiring=false;// Boolean value to tell if the plant is firing
				placedPlant.plantRow=plantRow;// plant row
				plantContainer.addChild(placedPlant);// adds the plant
				placedPlant.x=plantCol*65+57;
				placedPlant.y=plantRow*75+115;
				playerMoving=false;// tells the script the player is no longer moving
				movingPlant.removeEventListener(MouseEvent.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;// updates game array adding the new plant
			}
		}
		//
		// core function to be executed at every frame. The whole game is managed here
		//
		private function onEnterFrm(e:Event):void {
			var i:int;
			var j:int;
			//
			// plants management
			//
			for (i=0; i0) {
						// let's scan through all zombies
						for (j=0; j0; j++) {
							var targetZombie:zombieMc=zombieContainer.getChildByName(zombiesArray[currentPlant.plantRow][j]) as zombieMc;// gets the j-th zombie
							// if the zombie is on the right of the plant
							if (targetZombie.x>currentPlant.x) {
								var bullet:bulletMc=new bulletMc();// constructs a new bullet
								bulletContainer.addChild(bullet);// adds the bullet
								bullet.x=currentPlant.x;
								bullet.y=currentPlant.y;
								bullet.sonOf=currentPlant;// sets the bullet as a son of the current plant
								currentPlant.recharge=0;// the plant must recharge 
								currentPlant.isFiring=true;// the plant is firing
								break;// exits the j for loop
							}
						}
					}
				}
				// let's see if the plant has to recharge
				if (currentPlant.recharge650) {
					firingPlant.isFiring=false;// the plant is not longer firing
					bulletContainer.removeChild(movingBullet);// removes the bullet
				} else {
					for (j=0; j8||plantsArray[movingZombie.zombieRow][zombieColumn]==0) {
					movingZombie.x-=0.5;// moves each zombie left by 1/2 pixels
				} else {
					// the zombie attacks!!
					var attackedPlant:plantMc=plantContainer.getChildByName("plant_"+movingZombie.zombieRow+"_"+zombieColumn) as plantMc;
					attackedPlant.alpha-=0.01;// drains plant energy
					// let's see if the plant died
					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
					}
				}
			}
			//
			// suns management
			//
			for (i=0; i=0&&plantCol>=0&&plantRow<5&&plantCol<9) {
					selector.visible=true;// shows the selector
					selector.x=25+plantCol*65;
					selector.y=80+plantRow*75;
				} else {
					selector.visible=false;// hide the selector
				}
			}
		}
	}
}

In setupField function note how zombiesArray becomes an array of arrays, with one element for each row:

private function setupField():void {
	plantsArray=new Array();
	zombiesArray=new Array();
	for (var i:uint=0; i<5; i++) {
		plantsArray[i]=new Array();
		zombiesArray[i]=new Array();
		for (var j:uint=0; j<9; j++) {
			plantsArray[i][j]=0;
		}
	}
}

Then in newZombie function in zombiesArray we push the name of the zombie in the appropriate element according to zombie's row:

private function newZombie(e:TimerEvent):void {
	var zombie:zombieMc=new zombieMc();// constructs the zombie
	totalZombies++;
	zombieContainer.addChild(zombie);// adds the zombie
	zombie.zombieRow=Math.floor(Math.random()*5);// chooses a random row where to place the zombie
	zombie.name="zombie_"+totalZombies;//gives a name to the zombie
	zombiesArray[zombie.zombieRow].push(zombie.name);// adds the zombie in the row-th row
	zombie.x=660;// places the zombie on the board, outside the stage to the right
	zombie.y=zombie.zombieRow*75+115;
}

Now the whole process of determining if a plant can fire is a bit changed:

if (currentPlant.recharge==currentPlant.fireRate&&! currentPlant.isFiring) {
	// let's see if there are zombies in the same row of the plant
	if (zombiesArray[currentPlant.plantRow].length>0) {
		// let's scan through all zombies
		for (j=0; j0; j++) {
			var targetZombie:zombieMc=zombieContainer.getChildByName(zombiesArray[currentPlant.plantRow][j]) as zombieMc;// gets the j-th zombie
			// if the zombie is on the right of the plant
			if (targetZombie.x>currentPlant.x) {
				var bullet:bulletMc=new bulletMc();// constructs a new bullet
				bulletContainer.addChild(bullet);// adds the bullet
				bullet.x=currentPlant.x;
				bullet.y=currentPlant.y;
				bullet.sonOf=currentPlant;// sets the bullet as a son of the current plant
				currentPlant.recharge=0;// the plant must recharge 
				currentPlant.isFiring=true;// the plant is firing
				break;// exits the j for loop
			}
		}
	}
}

We are now scanning zombie by zombie in the same row the plant is placed on, looking for a zombie on the right of the plant (line 214).

Zombies now can attack with these simple lines:

var zombieColumn:int;
for (i=0; i8||plantsArray[movingZombie.zombieRow][zombieColumn]==0) {
		movingZombie.x-=0.5;// moves each zombie left by 1/2 pixels
	} else {
		// the zombie attacks!!
		var attackedPlant:plantMc=plantContainer.getChildByName("plant_"+movingZombie.zombieRow+"_"+zombieColumn) as plantMc;
		attackedPlant.alpha-=0.01;// drains plant energy
		// let's see if the plant died
		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
		}
	}
}

We see if there is a plant on the same tile the zombie is walking on, and if true the zombie stops walking and starts draining plant's life (alpha channel in this case).

This is the result:

You should know how to play. Try to get your plant killed by a zombie.

Download the source code. Next time, it will be interesting to describe attack waves and show real energy bars.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.