Making a Flash game like Plants Vs Zombies – Step 4

Read all posts about "" game

Welcome to step four. In this step we’ll make plants fire and eventually kill zombies.

Let’s start defining when a plant can fire:

* When there is at least one zombie on the same row the plant is placed
* When it’s not already firing (plants can fire only one bullet at once)
* When a certain amount of time passed since the last time the plant fired

Now let’s define the bullet life:

* The bullet flies from left to right
* The bullet is removed when it hits a zombie
* The bullet is removed when it flies outside the stage

These six concepts bring some great changes in our script, and I tried to organize it in the clearest way possible, while being conscious that putting all the code in a single class starts making the script a bit messy. Anyway, I tried to do my best to keep it readable.

Before showing you the script, you have to say I created a new object called bulletMc which is the bullet itself.

Ready to see an almost 300 lines long 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)
		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();
			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);
		}
		//
		// 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
			zombieContainer.addChild(zombie);// adds the zombie
			zombie.zombieRow=Math.floor(Math.random()*5);// chooses a random row where to place the zombie
			zombiesArray[zombie.zombieRow]++;// increases the number of zombies 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.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&¤tPlant.recharge==currentPlant.fireRate&&! currentPlant.isFiring) {
					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
				}
				// 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; j=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
				}
			}
		}
	}
}

If you followed previous steps you will notice I changed a bit the code but the old concepts remain the same.

I will focus on new concepts, starting from the creation of a new array called zombiesArray (line 18) which will store the number of zombies for every row. This is very useful when we want to know if a plant can fire.

At this time the plant can fire only when there is a zombie on its same row, so I don't care if the zombie is on its left or on its right, but it's something I will have to face during next step. I don't want to place a plant on the right of a zombie and see it firing to the right, to an empty row.

When a new zombie is added with newZombie function (line 100) these two lines

zombie.zombieRow=Math.floor(Math.random()*5);// chooses a random row where to place the zombie
zombiesArray[zombie.zombieRow]++;// increases the number of zombies in the row-th row

assign a zombieRow property to the zombie and increase the zombieRow-th element in zombiesArray array. Thanks to this array, I always know how many zombies are roaming in all possible rows.

In placePlant function (line 170), the function I use to place a plant, I am adding some custom properties:

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

I want to define the plant fire rate, in frames. It means the plant will fire a bullet every fireRate frames. I've chosen to measure the fire rate in frames rather than in milliseconds because if there are too much objects on the stage and the game slows down, the fire rate too will slow down, rather than remaining constant. recharge will be increased at every frame, and when it's equal to fireRate, the plant is ready to fire. isFiring tells us if the plant is already firing, and plantRow saves the row where the plant is placed, useful when it's time to check how many zombies are walking in its row.

To see if a plant can fire we use this if statement:

if (zombiesArray[currentPlant.plantRow]>0&¤tPlant.recharge==currentPlant.fireRate&&! currentPlant.isFiring) {
	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
}

It checks for the plant to be on the same row of at least one zombie, to be fully recharged and not to be already firing. Then a new bullet is created, some properties are changed to tell the script the plant is firing and has to recharge, but above all we need a sonOf property to know which plant fired the bullet. This is very useful once the bullet is about to be removed from the stage and we want to update isFiring property of the plant.

Then the core code for zombie killing management is this for loop:

for (j=0; j

I am scanning through all zombies performing an hit test to each one, and if a zombie has been hit, we remove the bullet, set isFiring property of the plant which fired it to false and decrease zombie's energy... the alpha in this case. If the alpha reaches zero, we remove the zombie and decrease the corresponding element in zombiesArray array to update the number of zombies in that row.

At this time you can test the game:

Collect money, buy plants and kill zombies.

Download the source code

Next time, we'll make zombies attack too, and we will give some environment to the game. No more circles Vs squares, going to make a real game out of it.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

214 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers