Plants vs Zombies in Unity with FlowCanvas
In order to improve my programming & my logic skills I decided to do a really small clone of Plants vs. Zombies in Unity using FlowCanvas.
I first found out about a tutorial like this on “noobtuts”. He uses actually code but I decided to try to look at the code and try to make that code using FlowCanvas. That particular Plants vs. Zombies tutorial is “PRO” and requires a subscription, but it was enough to inspire me and make my own tasks.
In this written article I will try to show screenshots and briefly try to explain what I wanted to accomplish, I do however recommend you to watch the YouTube video below because I will go into more details about the challenges I encountered.
Those are the mechanics I wanted to implement:
1. Have a field with grass tiles where the plants can be placed on.
2. Implement the Sunflower. The Sunflower will spawn a sun at a fixed interval.
3. Implement the FiringPlant. When the plant detects a zombie, it starts firing a bullet.
4. UI Buttons on top that show the plants.
5. When we click an UI Button, show the plant next to the cursor so the user knows that he clicked the plant. Right click cancels the clicked plant.
6. When we have a plant selected, and we click a grass tile, place that plant on that tile.
7. Have a “currency” mechanic. Each plant costs money. The sun that the plant spawns, will give some money to the player.
8. Zombie Spawner. It will randomly spawn a zombie on each lane every X seconds.
9. Implement the Zombie. Zombie will walk and when he encounters a plant it will start to attack the plant.
Gameplay wise this are some really basic features. With what I have there I don’t even have a proper game. No win state, no fail state. But that’s ok. I only wanted to implement a couple of features to see how it goes, then move to something else. I have the tendency to get bored fast if the project has no actual good meaning… so I tried to keep the “features list” small.
Even that he list might seem small, you will be amazed on how complex things get fast. One basic feature might actually spawn a good other couple of smaller features. The reason why I wrote the word “sun” & “bullet” in italic is because those items to will have some logic to even that at first sight to an inexperienced developer it might just be irrelevant. Yea … the plant will spawn a bullet so what? Well the bullet has to instantiate form a place, travel with a speed, have a collider, detect when it hits something… and make sure that THAT SOMETHING is an actual Zombie and not another plant or another bullet. But then, when we detect that we hit a zombie… then what? Oh… we have to deal some damage to the zombie? What does that mean? Well, the bullet needs to have “damage” and the zombie needs to have “health”. OOooohhhh…. well if the Zombie also deals damage, it means that he himself will need to have “damage”, but also the plants will need to have “health”.
And this is just a small example that some people might not even think about.
Grass Tile Logic
The first think I did was to create that “grass tiles”. I simply created a sprite that has a BoxCollider2D set up as Trigger. I then set them up into an EmptyGameObject named Column… then I just duplicated it. It looks something like this:
I made the Grass Tile a prefab because I might want to modify some settings later. Also, the Grass Tile itself will have a FlowCanvas script that looks like this:
The reason why we do this is so we know what “Grass Tile” we click so we know to place a Plant on that tile. We use an event called Mouse2D. When the mouse is “Down” it will trigger. We then get to that Switch Condition. This one is like an “IF”. You can do “True & False” stuff. We check there to see if the Grass Tile has children. If he has children, we do nothing. The reason why we do this is that… when we put a plant on a tile, that plant will be a children of the Grass Tile. So basically, we are checking to see if the the Grass Tile already has a plant. If he has a plant, we do nothing!
Once we know that we have no plant on the Grass Tile, we Send an Event called “clickedGrass“. The event type is a “Game Object” type. In FlowCanvas you can send various events. This time we send a Game Object event because we want our GAME_MANAGER to receive the Game Object that was clicked… in more human terms, we want to receive the Grass Tile we clicked.
You might notice that the “Target” of the Send Event is not our Game Manger. We use the function “FindGameObjectWithTag” and we search for the Tag “Game Manager”. As you might have guessed, we only have 1 object that has that tag, our GAME_MANAGER GameObject. The reason why we do this is that we CAN NOT “hardcode” scene information into a prefab. Remeber that our Grass Tile is a prefab. It’s an “Asset File” in our project. Asset Files can not know information about the Scene… & our GAME_MANAGER is an GameObject in our scene. So basically whenver we click we do a FindGameObjectWithTag, we find our GAME_MANAGER from our Scene and we send him the information… we send him the clicked “Grass Tile” game object.
Ok, this one is fairly big. One of the reasons why I didn’t started with this one. This is just an “EmptyGameObject” that has a FlowCanvas Controller Script… so I guess it’s not an “EmptyGameObject” anymore 😀 Anyway…
The main idea of the GAME_MANAGER is to handle everything. Small GameObjects like the plants, grass tiles, zombie, etc… they have their own logic, but they usually send something to the main GAME_MANAGER who will do something with that information.
Continuing with the previous example. The “Grass Tile” is sending an event with the grass tile Game Object that was clicked. Then, the GAME_MANAGER does this:
It basically takes the received Game Object and stores it into $clickedGrass variable that is of type Game Object. Then we do a Switch check. We check to see if we have a Plant Selected. If we don’t have a Plant Selected, we don’t do anything since we didn’t select any plant to build. However, if we DO have a plant selected, we send an event to “itself” that will “checkCurrentMoney“.
Hey, if you want to BUILD a plant, you have to Pay the price!
Ok, let me go a bit back because you might be a bit confused. I started like this because I wanted to show you how you can send events between game objects and actually do something. However the whole GAME_MANAGER is a “beast”! It currently looks like this:
So there are a couple of things that are going on. So in order to understand better what I’m trying to do I’m going to try to explain an “overall”.
Right now the game looks like this:
You can see the Grass Tiles we just created. That zombie is placed there just for “scale purposes”. We have “Money” in top left corner… but we don’t see the actual money we have. We update the money we have at Run Time. Then we have 2 buttons with the plants that we can build.
Whenever we click a Plant Button, we store the “clickedPlant” into a variable, then we do something something and store the appropriate Prefab also into another variable.
Whenever we click a Grass Tile, we do that checkup we already talked before, and if we have a plant stored into variables, we check to see if we don’t already have a plant on that Grass Tile, if we don’t, we instantiate the correct plant onto that Grass Tile.
However, before we instantiate the correct plant onto that Grass Tile, we check to see if we have enough money to build that plant… if we do, of course that we will instantiate that plant.
So as you can see there is a small system here that has to communicate around to do some small checkups before we even place the plant down!
Now that you understand what we have to accomplish here, let’s see how we do it.
Plant UI Buttons
All the UI Buttons have a Canvas that can be seen above. When the UI Button (SunflowerBtn) get’s clicked it sends an event to the GAME_MANAGER. The event name is called “selectedPlant” and the Event Value is “sunflower”. The selectedPlant event from the GAME_MANAGER can be seen below.
When the “selectedPlant” event get’s triggered, it get’s the received value and stores it into the variable called $selectedPlant. We will use this variable in multiple places. After that we use a Switch String node that can have multiple values. If the value is for example “sunflower”, we SET the variable $selectedPlantGO (the GO comes from Game Object) to the “Sunflower” prefab. I drag & dropped the Prefab into the canvas in order to use it.
So let me explain again. We click the UI Button and we send a string of that button to the game manager. The game manager takes that string and compares it, and depending on the string we set a game object variable to the prefab we want to use. If even more simpler terms. We click the SUNFLOWER button, we set the variable $selectedPlantGO to use the SUNFLOWER prefab. We will use this variable later, when we actually want to place the plant down… and we need the actual Game Object to instantiate and not just a string.
Right Click Function (Cancel selection)
This could have been named better but when I first named it it was only used when I clicked the right mouse button. The main idea here is to “Cancel your current selection when you right click”.
For example, you click the UI Button selecting a plant. However I want to cancel the selection in order to use my “left mouse button” for other things. Like clicking on a sunflower’s sun. I don’t want to accidentally click a grass tile and build a plant I don’t want. So the idea of the “Right Click Function” is to empty the variables we usually set when we click the UI Button.
This function get’s triggered when the custom event “rightClicked” is called (I added this later) or when the Mouse Button “right” is pressed. I have a Switch Condition that checks if the $selectedPlant variable is == to “null”. I only want to set everything to “Null” when they actually have a value and not every time I press the rightClick button.
Check if the player has money to build a plant
Ok, so let’s see what we have so far. We know what Grass Tile we click when we click a grass tile. We know what Plant Button we clicked so we know what “Plant to Build”… and you already saw the “grassGotClicked” function. This function checks to see if you have a plant selected, and if you do it check to see if you have the money to build that plant.
So this is what happens here. We have a Switch Condition that check to see if you have money. If you don’t have money we just have a Debug Log text that prints “You Don’t Have Money”. Normally you would like to have a sound & a visual text to appear telling the player that he has no money to build that plant. The ideal solution however would be to to not allow the player to click the UI Button if he has no money, but that’s a story for another time.
Ok… so we have $currentMoney which is a variable that holds the money that we currently have. We check to see if the plant cost is below or equal to our $currentMoney. If it’s bellow it means we have money to build the plant.
We already have the $selectedPlantGO set with the plant we want to get. The main problem here is… how do we access variables from other Blackboards? Here is how I did it here. Go to the Blackboard (script) in the inspector panel and drag & drop the Blackboard (script) into the Canvas. Then you select Methods > GetVariable.
After you do that, select the node then you have to configure it. Select the “Of Type” to System.Int32 (since our variable is an Integer) & the name to “plantPrice” since this is the variable we want to get. To the “blackboard” we area already feeding the $selectedPlantGO which we know already has a Blackboard script attached. So it will take the Variable of the plant we currently have selected. Easy.
After we made sure that we clicked the UI button, clicked a grass, have the money… it’s time to finally spawn/build/instantiate the plant.
For that we will use the node called UnityObject.Instantiate. That node requires the object to instantiate and a position. We already have the object we want to instantiate in the $selectedPlantGO variable. The position we get by getting the position of the $clickedGrass. That way we spawn the plant right on the position of the Clicked Grass. However…
We get this new instantiated object and store it into $newPlant variable. We will use this later. Then we want to set the Parent of the new instantiated object… we don’t want our plants to be in the “root” of our hierarchy. We want for our plants to be Childs of the clickedGrass. Remember? We do a check to see if we already have a plant on the clicked grass… that way we can’t build multiple plants on the same Grass Tile. Of course that there are other options to do this, but right now I think that the “Adding the plant as a child” is enough. It might be a problem in case we need to have multiple children of the same grass, but in our simple “game” that will not be the case.
Anyway… we use the node Set Parent that requires a Transform. We feed it the value that came from the UnityObject.Instantiate, aka the object we just spawned. And for the Parent we specify the $clickedGrass.
After we set the parent, it’s time to get our current money and substract the cost of the plant we just build.
In the end we do a “rightClick” event. Remember… that event that Null-ifies the variables we populate when we click the UI Button. The main idea here is that you click a plant button, you build… but then you selection get’s emptied so if you want to build again you have to click the button again.
How that we have all the “build plant” logic, let’s see the plants themselves. In PvZ you gain “Money” by clicking the “suns” that get randomly spawned. They usually come from the Sunflower Plant, but in the base PvZ game random suns also spawn around the map. In our case we only get suns (aka Money) from the Sunflower Plants.
The Plant itself is a game object that I made into a Prefab. It has an Animator because the plant has an Idle animation, it has the FlowCanvas Script, a Blackboard that has some variables that we use and a BoxColider2D set to Trigger.
When the plant is active, the above Canvas get’s active and runs On Fixed Update. What we want to accomplish is to spawn a sun every X seconds. For that we use the Wait node. It requires a “Time” variable… in our case it’s the $spawnSunTimer. Then the Wait has 3 different options. Start / Update / Finish.
Start get’s called right at the start of the Wait node. We don’t want that, because we don’t want to spawn a sun immediately when we build the plant. Update get’s called every … Unity’s Update. This is good when you want to have an effect active for a duration and you want to call that effect on each update. Finish is called at the end of our time.
Basically we reach the Wait node, a counter starts… when the counter reaches 0 our Finish function get’s called.
When we reach 0, we call UnityObject.Instantiate … where we instantiate the Sun which is a prefab, and we get the Position of SELF. Meaning that we get the position of the plant, and we spawn the sun at the plant’s location. Easy!
Ok. The Sun itself has a couple of things going. First of all we have a variable called “getMoney”. Not the best name probably. This is the value that we will get when we click the sun.
Now that the Sun has spawned, we have a “OnFixedUpdate” that “Add’s Force”. I don’t really like this anymore because I have to thinker with the physics. I used this with the Zombies also but later I changed it and I used Translate. The Sun however remained the same. So anyway… we add 0.3 Force on the Y axis… meaning that we give the Sun a force… so it moves UP.
Then we have a Mouse2D event. This one get’s triggered when we Click the sun. When we click the sun we send an event to the Game Manager. The event itself it’s called sunClicked. After that we cal UnityObject.Destroy … and we destroy SELF. Meaning that we destroy the sun after we click it.
Another thing that we do is to Destroy the sun after 8 seconds. The Sun’s will still remain in memory if the player does not click them and we do not want that. Ideally you would want detect when the suns get outside the window and then destroy them. The way I did it it’s a bit “hardcoded”. I had to manually check the speed of the sun, put a sunflower on the bottom tile… let the sun spawn and see how much time it will take it to reach the end of the screen. For example, if we have a Sunflower on the top of the grid, and a sun get’s spawned, it will reach the end of the screen much faster… however the sun will still remain in the memory for 8 seconds. So not really ideal.
This is the sunClicked event. It Set’s the $currentMoney to $currentMoney + the received value from the Sun.
We talked a lot about Money. How we check for money, how we set the money… but there are some other things to talk about. How do we update the UI??
You remember how on the default game screenshot you see on the UI “New Text”. I said that we were going to update that text later. Well, we update the text “On Enable”. When the game starts we send an event called “updateMoney” with our $currentMoney variable.
The event “updateMoney” just set’s the text from our UI to the received value.
Then we also have a custom event called “On Variable Change”. This is an event that I requested last year. Basically, whenever we detect that the variable $currentMoney has changed, we send an event to the updateMoney with the new value. Pretty intuitive & easy.
Now lets talk about the Firing Plant. This is the plant that will shoot a bullet that would damage a zombie. Because this plant has 2 animation states it’s a bit more “complex” than the Sun Flower.
So let’s look at the animator.
We have a Firingplant_Idle animation state that will play the Idle animation & a Firingplant_Fire animation that will play the Fireanimation. We also have an “isFiring” parameter that is of Bool type.
If you click the arrow between the states you can see the transition. The most important here is the conditions. In order for us to go from the Idle state to the Fire state the isFiring has to be TRUE. We will change the state of that isFiring parameter trough code… later.
Now we get to the parts that caused me the most pain. Not that it’s complicated but it was a bit hard to debug and know exactly what to use. Also, since I have done this like 2 months ago I kind of forgot some stuff so I will try to remember 😀
What we try to do here is do a Linecast. That’s an invisible line that goes from the plant to wherever we tell that like to go. When that line detects a Zombie, we want to start firing.
So we will cast the line every fixed update. We first do a Debug.DrawLine. Keep in mind that the line is invisible, even for us, so that’s why we use that. It requires a Start and an End and a color. For many days I wasn’t sure why this wasn’t working. You know why? Because the ALPHA of the FUCKING COLOR was SET TO ZERO!
After the Debug.DrawLine we move to a Switch Condition that checks to see if we “Get Transform – RaycastHit2D” basically… we check to see if we hit something. So walking a bit backwards… you can also see there that we use a Phyrics2D.Linecast. That’s our main linecast. It also uses the same parameters as the Debug.DrawLine but also requires a Layer Mask.
So about the parameters. We require a Vector3, however the Phyrics2D.Linecasts require a Vector2. So we get SELF … where we extract the Vector3. On the part from above we do a + 0.5. We add 0.5 from the middle of our plant because we want to avoid the Linecast colliding with our own collider. You can imagine how much fun I had with this 😀 With that info we build a New Vector3 that we feed into the Debug.DrawLine. On the part from below, we add 10.5. This is basically how long we want the Linecast to be.
Since we already Extracted the Vector3, we take the X & Y and we do a New Vector2 that we feed into the Phyiscs2D.Linecast.
Another thing that was super hard to figure it out .. was the Layer Mask. I had countless of tests when I wasn’t sure why it was firing, why it was not firing… trying Trigger Colliders, normal colliders… etc. So I was sure that I want to crate a special Layer, and check for collision ONLY if we are on that Layer. Sometimes the Linecast was triggering because it was seeing the bullet from the plant… so it was constantly firing even when there was no zombie 😐
So what I found out is that I can create a New Variable > Unity Engine > LayerMask. So yea, a variable of type LayerMask. That’s where I feed it the Zombie Layer… and I was able to get that variable on the Canvas and feed that to the Phyrics2D.Linecast. But before that I had to Get value of the ZombieLayerMask.
After all this we have a Switch String where we check for “Zombie”. We do this by Getting the Tag of the zombie. If everything checks out we set a BOOL variable called $isFiring to true. It is a different variable than the parameter from the Animator! Same name but different!
This is the second part of the FiringPlant Canvas. We have another Fixed Update that is constantly running checking to see if the $isFiring is True. When it finally detects that it’s true, it will Set Bool of the Animator parameter isFiring to true! Remember that parameter isFiring from the Animator? Here is where we set it to true… so the firing animation can begin.
After that we do a Wait that will “instantiate” a bullet every $fireRate. When we detect that there is no Zombie in sight, the first part of the Canvas will set the $isFiring to False. When that happens this will Set Bool of the animator to False, so the plant goes back to the Idle animation. We will also Break the Wait node so it will stop instantiating bullets.
Firing Plant Bullet
This is the Bullet that the Firingplant instantiates. It has to variables. Damage & Speed. Damage is how much damage the bullet will deal & the speed is how fast the bullet will travel.
The collider has to be set up as Trigger. We don’t want it to affect other game objects that have colliders.
Then, the Rigidbody 2D has to be setup as Simulated. There was some weird thing that you need to have a Rigidbody2D even if you don’t use functions like Add Force…otherwise the Triggering does not trigger. I remember doing some testing didn’t knew why stuff didn’t happen 🙁
So let’s talk about the bullet movement. I said before when I was talking about the Sunflower’s Sun that I was using there Add Force but I stopped doing it like that. I decided to use Transform.Translate. “Moves the transform in the direction and distance of translation.” Sounds a bit weird and confusing…
Now let’s talk about the Trigger. We use a Trigger2D event. Whenever we collide with something and we trigger the event, we do a Switch String and check the TAG of the object we collided with. Lot’s of tests happened here and I didn’t knew exactly why it worked strangely. Well… remember how the Grass Tiles also have colliders? Well I was also colliding with the grass & stuff like that 😀 So we Get Tag of the object we collide with, and when we detect the TAG to be Zombie we do this:
We send an event called “receiveDamage”. The target for the event is the object we collided with, in our case the Zombie. So the zombie will receive that “receiveDamage” event. We will see what this event does later. The event type is Integer because we want to send an Integer value… the value that we send is the Damage that the bullet did to the zombie. After we send the event we destroy the bullet.
Thinking about this … if a bullet does not hit zombie, the bullet will always be instantiated. We should have also done some kind of Timed destroy or detect when the bullet collides with the edge of the screen.
This is how we move the Zombie. On Fixed Update we check to see if the Zombie is attacking. If the zombie is attacking we don’t want him to move 🙂 If he is not attacking we use Transform.Translate. As Translation we use Delta Timed Vector3 and we feed it the $speed. Basically there we tell the zombie a value. We tell the zombie to move -1 (because we want him to come from Right to Left) on the X axis. However we need to multiply that value with a number in order to control it’s speed… so we make the zombie move faster or slower. The speed of the zombie right now is 0.3.
Those are the variables that the Zombie has. A health, speed, hitObject is the object that the Zombie will DPS if he hits one (when the zombie hits a plant… what plant did he hit?), a bool called isAttacking so we know when he is attacking… and DPS… the damage he will deal. So it shoudn’t be called DPS because DPS = Damage per Second, but in that value we don’t have a second. I didn’t implemented the DPS so this is why this variable is incorrectly. Normally we would have a Damage variable & a AttackTime variable…which tells the zombie how fast to attack. Those 2 variables become the DPS.
Whenever the zombie is hit by a bullet this is the event that get’s called. We get the we set the Health to CurrentHealth – the received value (the damage the zombie will receive). Then we do a check to see how much life the Zombie has left. If it has Less and/or Equal to 0 we also call UnityObject.Destroy and we destroy SELF (the Zombie).
So this is the rest & unfinished Zombie logic. We have a Trigger2D where we compare the Tag and check when we hit a game object with the TAG Plant. When we hit an object we set the $hitObject to the plant we just hit then we send an event to SELF called start Attacking.
The startAttacking event sets the $isAttacking bool value to True then it sets the Zombie Animator parameter isAttacking to true and sends an event to SELF called DealDamage.
The Zombie itself also has an animator like the firing plant.
This is where I stopped. Next step was to do the Deal Damage, send an event to the plant to receive damage… and so on. Those stuff are kind of the same so I could have turned those into a Macro. But that’s for another episode.
This is the Zombie Spawner. Normally you want to have more control for a spawner. You need to give the game designer more control when and what zombie to spawn. How many zombies to spawan, etc. This is just a thing that every 3 seconds it sends an event to the “pickRandomSpawner”.
The “pickRandomSpawner” basically sends an event to the Spawner with the name “spawnZombie”.
A ZombieSpawner is just a game object placed on each “Row”. The reason why they are 6 when we have 5 rows is because… somehow… don’t know exactly WHYYYY… a zombie never spawns on the last row.
The Zombie Spawner is pretty easy. It just Instantiates an object (our Zombie Prefab) in our current position.
Add a slight random delay before starting to fire to avoid symmetry in the bullets. You could also randomize the bullet spawn point so the bullet will be a bit higher/lower.
Another thing that I wanted to do was to … show a “Plant Sprite” when we click a Plant Button. That way the player has a visual feedback on what button he clicked.
This is an unfinished function. Right now all it does is move the Zombie I have in my screen. In order to do this you need to instantiate the plants and put them somewhere offscreen. When you trigger this, we need to get that offscreen plant and we move it to “Input.mousePosition”. Then when we do that “rightClick” function, we also move the plant back where it was.
This is when I stopped. There are a lot of other stuff to implement of course. This isn’t even a game. I just wanted to implement some small stuff and this is what I did.
If you have questions please ask! I know this whole tutorial is not that greatly written… but I will try to improve.
You can get FlowCanvas by clicking this thing below…