Talking about Plants Vs Zombies game, Actionscript 3, Flash and Game development.
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.
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.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.