How To Create A Tower Defense Game In Unity
Tower defense games are becoming increasingly popular, and this is not surprising - not much can be compared with the pleasure of observing your own lines of defense, destroying evil enemies! In this two-part tutorial, we will create a tower defense game on the Unity engine !
You will learn how to do the following:
- Create waves of enemies
- Make them follow route points.
- Build and upgrade towers, as well as teach them how to break enemies into small pixels
At the end we will get a game frame that can be developed further!
Note : you need some basic knowledge of Unity (for example, you need to know how assets and components are added, what prefabs are) and the basics of the C # language . To learn all of this, I recommend that you go through the Unity tutorials on Sean Duffy or the Beginning C # with Unity series by Brian Mockley.
I will work in the Unity version for OS X, but this tutorial is also suitable for Windows.
Through the windows of the ivory tower
In this tutorial, we will create a tower defense game in which enemies (little bugs) crawl to the cookies belonging to you and your minions (of course, these are monsters!). The player can place monsters in strategic points and improve them for gold.
The player must kill all the bugs until they get to the cookie. Every new wave of enemies is harder to beat. The game ends when you survive all the waves (victory!) Or when five enemies crawl to the cookies (loss!).
Here is a screenshot of the finished game:
Monsters, unite! Protect the cookie!
Getting Started
Download this procurement project , unzip it and open the project TowerDefense-Part1-Starter in Unity.
In the preparation of the project there are graphics and sound assets, ready-made animations and several useful scripts. The scripts are not directly related to the tower defense games, so I'm not going to talk about them here. However, if you want to learn more about creating 2D animations in Unity, then learn this tutorial on Unity 2D .
The project also contains prefabs, which we later add to create characters. Finally, there is a scene in the project with a background and a customized user interface.
Open GameScene , located in the Scenes folder and set the aspect ratio for Game mode4: 3 so that all the tags match the background correctly. In Game mode, you will see the following:
Authorship:
- Graphics for the project is taken from the free pack of Vika Wenderlich! Other graphic works can be found on her gameartguppy site .
- Great music taken from the BenSound website , which has other amazing soundtracks!
- Thanks also to Michael Jesper for a very useful camera shake function .
.
The place is marked with a cross: the location of the monsters
Monsters can only be put on dots marked with x .
To add them to the scene, drag Images \ Objects \ Openspot from the Project Browser to the Scene window . While the position for us is not important.
After selecting Openspot in the hierarchy , click on Add Component in the Inspector and select Box Collider 2D . In the Scene window, Unity displays a rectangular collider with a green line. We will use this collider to recognize mouse clicks on this place.
Similarly, add the Audio \ Audio Source component to Openspot . Select for the AudioClip component Audio Source file tower_place , which is in the folder, the Audio , and disable the Play the On Awake . We need to create 11 more points. Although there is a temptation to repeat all these actions, there is a better solution in Unity: Prefab ! Drag Openspot from the Hierarchy to the Prefabs folder inside the Project Browser . His name will become blue in the Hierarchy, which means that it is attached to the prefab. Like that:
Now that we have a prefab blank, we can create as many copies as we like. Simply drag Openspot folder Prefabs within the Project Browser window the Scene . Repeat this 11 times, and we will have 12 Openspot objects in the scene.
Now use the Inspector to set the following coordinates to these 12 Openspot objects:
- (X: -5.2, Y: 3.5, Z: 0)
- (X: -2.2, Y: 3.5, Z: 0)
- (X: 0.8, Y: 3.5, Z: 0)
- (X: 3.8, Y: 3.5, Z: 0)
- (X: -3.8, Y: 0.4, Z: 0)
- (X: -0.8, Y: 0.4, Z: 0)
- (X: 2.2, Y: 0.4, Z: 0)
- (X: 5.2, Y: 0.4, Z: 0)
- (X: -5.2, Y: -3.0, Z: 0)
- (X: -2.2, Y: -3.0, Z: 0)
- (X: 0.8, Y: -3.0, Z: 0)
- (X: 3.8, Y: -3.0, Z: 0)
When you do this, the scene will look like this:
Placing monsters
To simplify placement, there is a Monster prefab in the Prefab project folder .
Monster's prefab is ready for use
. At the moment it consists of an empty game object with three different sprites and shooting animations as child elements.
Each sprite is a monster with different levels of power. Also in the prefab contains the component Audio Source , which will be launched to play the sound when the monster shoots a laser.
Now we will create a script that will be placed by Monster on Openspot .
In the Project Browser , select the folder Prefabs object Openspot . In the Inspector, click on Add Component , and then select New Script.and name the script PlaceMonster . Select Sharp as the C language and click on Create and Add . Since we added the script to the Openspot prefab, all Openspot objects in the scene will now have this script. Fine!
Double-click the script to open it in the IDE. Then add two variables:
public GameObject monsterPrefab; private GameObject monster;
We will create an instance of the object stored in monsterPrefab
, to create a monster, and save it in monster
so that you can manipulate it during the game.
One monster per point
To place only one monster on one point, add the following method:
private bool CanPlaceMonster ( ) { return monster == null ; }
In CanPlaceMonster()
we can check whether the variable monster
is still equal null
. If so, then there is no monster at the point, and we can place it.
Now add the following code to place the monster when the player clicks on this GameObject:
//1 void OnMouseUp ( ) { //2 if (CanPlaceMonster()) { //3 monster = (GameObject) Instantiate(monsterPrefab, transform.position, Quaternion.identity); //4 AudioSource audioSource = gameObject.GetComponent<AudioSource>(); audioSource.PlayOneShot(audioSource.clip); // TODO: вычитать золото } }
This code places the monster when you click the mouse or touch the screen. How does he work?
- Unity automatically triggers
OnMouseUp
when a player touches a physical GameObject collider. - When called, this method puts a monster if it
CanPlaceMonster()
returnstrue
. - We create a monster using the method
Instantiate
that creates an instance of a given prefab with the specified position and rotation. In this case, we copymonsterPrefab
, set the current position of the GameObject and the absence of rotation to it, transfer the result toGameObject
and save it tomonster
- At the end, we call
PlayOneShot
to play a sound effect attached to a component of anAudioSource
object.
Now our script PlaceMonster
may have a new monster, but we still need to specify the prefab.
Using the right prefab
Save the file and return to Unity.
To set the monsterPrefab variable , first select the Openspot object from the Prefabs folder in the project browser .
In the Inspector, click on the circle to the right of the Monster Prefab field of the PlaceMonster (Script) component and select Monster in the dialog that appears .
That's all. Run the scene and create monsters in different places by clicking the mouse or touching the screen.
Fine! Now we can create monsters. However, they look like weird porridge, because all the child sprites of the monster are drawn. Now we fix it.
Raise the level of monsters
The figure below shows that as the level rises the monsters look more and more frightening.
What a sweetheart! But if you try to steal his cookies, this monster will turn into a killer.
The script is used as the basis for the implementation of the system of levels of monsters. He tracks the power of the monster at each level and, of course, the current level of the monster.
Add this script.
Select the Prefabs / Monster prefab in the Project Browser . Add a new C # script called MonsterData . Open the script in the IDE and add the following code above the class . MonsterData
[ System.Serializable ] public class MonsterLevel { public int cost; public GameObject visualization; }
So we create MonsterLevel
. It groups the price (in gold, which we will support below) and the visual representation of the monster level.
We add on top [System.Serializable]
so that instances of the class can be changed in the inspector. This allows us to quickly change all values of the Level class, even when the game is running. This is incredibly useful for balancing the game.
Setting monster levels
In our case, we will store the specified MonsterLevel
in List<T>
.
Why not just use it MonsterLevel[]
? We will need an index of a particular object several times MonsterLevel
. Although it is easy to write code for this, we still have to use the IndexOf()
implementation functionality Lists
. It makes no sense to reinvent the wheel.
Reinventing a bicycle is usually a bad idea.
At the top of MonsterData.cs, add the following construct using
:
using System.Collections.Generic;
It gives us access to generalized data structures so that we can use the class in the script List<T>
.
Note : generalizations are a powerful concept for C #. They allow you to set type-safe data structures without adhering to the type. This is useful for container classes such as lists and sets. To learn more about generalized structures, read the book Introduction to C # Generics .
Now add the following variable MonsterData
to store the list MonsterLevel
:
public List<MonsterLevel> levels;
Due to the generalizations we can guarantee that List
out level
will contain only objects MonsterLevel
.
Save the file and switch to Unity to configure each level.
Select Prefabs / Monster in the Project Browser . The Inspector now displays the Levels field of the MonsterData (Script) component . Set the size parameter to 3 .
Next, set the cost for each level:
- Element 0 : 200
- Element 1 : 110
- Element 2 : 120
Now assign the values of the visual display fields.
Deploy the Prefabs / Monster in the Project browser to see its children. Drag a child Monster0 into the visualization Element 0 field .
Next, set Element 1 to Monster1 , and Element 2 to Monster2 . The GIF shows this process:
When you select Prefabs / Monster , the prefab should look like this:
Setting current level
Go back to MonsterData.cs in the IDE and add to MonsterData
another variable.
private MonsterLevel currentLevel;
In a private variable, currentLevel
we will store the current monster level.
Now we currentLevel
will set and make it visible for other scripts. Add the following lines to the MonsterData
declaration of the instance variables:
//1 public MonsterLevel CurrentLevel { //2 get { return currentLevel; } //3 set { currentLevel = value ; int currentLevelIndex = levels.IndexOf(currentLevel); GameObject levelVisualization = levels[currentLevelIndex].visualization; for ( int i = 0 ; i < levels.Count; i++) { if (levelVisualization != null ) { if (i == currentLevelIndex) { levels[i].visualization.SetActive( true ); } else { levels[i].visualization.SetActive( false ); } } } } }
Quite a big piece of C # code, right? Let's sort it out in order:
- Set the property of a private variable
currentLevel
. By setting a property, we can call it like any other variable: either asCurrentLevel
(within the class) or asmonster.CurrentLevel
(beyond). We can define any behavior in a getter or setter method, and by creating only a getter, setter, or both, we can control the characteristics of the property: read-only, write-only, and write / read. - In the getter, we return the value
currentLevel
. - In the setter, we assign a
currentLevel
new value. Then we get the index of the current level. Finally, we cycle through all levels and turn on / off the visual display depending oncurrentLevelIndex
. This is great because when you change thecurrentLevel
sprite is updated automatically. Properties is a very handy thing!
Add the following implementation OnEnable
:
void OnEnable ( ) { CurrentLevel = levels[ 0 ]; }
Here we ask when placing CurrentLevel
. This ensures that only the desired sprite will be shown.
Note : It is important to initialize the property inOnEnable
, not inOnStart
, because we call ordinal methods when creating prefab instances.
OnEnable
will be called immediately when creating a prefab (if the prefab was saved in the enabled state), butOnStart
not called until the object starts to run as part of the scene.We need to verify this data before placing the monster, so we initialize it in
OnEnable
.
Save the file and return to Unity. Run the project and position the monsters; they now display the correct sprites of the lowest level.
Monster upgrade
Return to the IDE and add to the MonsterData
following method:
public MonsterLevel GetNextLevel ( ) { int currentLevelIndex = levels.IndexOf (currentLevel); int maxLevelIndex = levels.Count - 1 ; if (currentLevelIndex < maxLevelIndex) { return levels[currentLevelIndex+ 1 ]; } else { return null ; } }
In GetNextLevel
we get the index currentLevel
and the index of the highest level; if the monster has not reached the maximum level, the next level is returned. Otherwise returns null
.
You can use this method to find out if monster upgrades are possible.
To increase the level of the monster, add the following method:
public void IncreaseLevel ( ) { int currentLevelIndex = levels.IndexOf(currentLevel); if (currentLevelIndex < levels.Count - 1 ) { CurrentLevel = levels[currentLevelIndex + 1 ]; } }
Here we get the index of the current level, and then we see that this is not the maximum level, checking that it is smaller levels.Count - 1
. If so, then assign the CurrentLevel
value of the next level.
Checking upgrades functionality
Save the file and return to PlaceMonster.cs in the IDE. Add a new method:
private bool CanUpgradeMonster ( ) { if (monster != null ) { MonsterData monsterData = monster.GetComponent<MonsterData>(); MonsterLevel nextLevel = monsterData.GetNextLevel(); if (nextLevel != null ) { return true ; } } return false ; }
First we check if there is a monster that can be improved by comparing the variable monster
with null
. If this is true, then we get the current monster level from it MonsterData
.
Then we check whether the next level is available, that is, whether it returns a GetNextLevel()
value null
. If a level increase is possible, then we return true
; otherwise return false
.
Implement improvements for gold
To enable the upgrade option, add to the OnMouseUp
branch else if
:
if (CanPlaceMonster()) { // Здесь код остаётся таким же } else if (CanUpgradeMonster()) { monster.GetComponent<MonsterData>().IncreaseLevel(); AudioSource audioSource = gameObject.GetComponent<AudioSource>(); audioSource.PlayOneShot(audioSource.clip); // TODO: вычитать золото }
We check the possibility of upgrading with CanUpgradeMonster()
. If possible, we access the component MonsterData
with GetComponent()
and call it IncreaseLevel()
, which increases the level of the monster. Finally, we run the monster's AudioSource .
Save the file and return to Unity. Start the game, place and improve any number of monsters (but this is for now).
Pay Gold - Game Manager
While we can immediately build and improve any monsters, but will it be interesting in the game?
Let's look at the question of gold. The problem with tracking it is that we have to transfer information between different game objects.
The figure below shows all the objects that should take part in this.
All selected game objects must know how much gold the player has.
To store this data, we will use a shared object that other objects can access.
Right-click on the Hierarchy and select Create Empty . Name the new GameManager object .
Add a new C # script to GameManager called GameManagerBehavior , and then open it in the IDE. We will display the total amount of player gold in the label, so add the following line at the top of the file:
using UnityEngine.UI;
This will allow us to access classes like UI Text
, which is used for labels. Now add the following variable to the class:
public Text goldLabel;
It will contain a link to the component Text
used to display the amount of gold the player has.
Now, when he GameManager
knows about the label, how do we synchronize the amount of gold stored in a variable and the value displayed in the label? We will create a property.
Add to the GameManagerBehavior
following code:
private int gold; public int Gold { get { return gold; } set { gold = value ; goldLabel.GetComponent<Text>().text = "GOLD: " + gold; } }
Does he seem familiar? The code is similar to CurrentLevel
that we asked in Monster
. First we create a private variable gold
to store the current amount of gold. Then we set the property Gold
(unexpectedly, right?) And implement the getter and setter.
Getter simply returns value gold
. Setter is more interesting. In addition to setting the value of a variable, it also sets the field text
for goldLabel
to display the new value of gold.
How generous will we be? Add to the Start()
following line to give the player 1000 gold, or less if you feel sorry for the money:
Gold = 1000 ;
Assigning a Label Object to a Script
Save the file and return to Unity. In the Hierarchy, select the GameManager . In the Inspector, click on the circle to the right of the Gold Label . In the Select Text dialog box, select the Scene tab and select GoldLabel .
Run the scene and Gold: 1000 appears in the label .
Check the player's wallet
Open the PlaceMonster.cs script in the IDE and add the following instance variable:
private GameManagerBehavior gameManager;
We will use gameManager
to access the component GameManagerBehavior
object GameManager scene. To set it, add to the Start()
following:
gameManager = GameObject.Find( "GameManager" ).GetComponent<GameManagerBehavior>();
We get a GameObject called GameManager using the function GameObject.Find()
that returns the first found game object with that name. Then we get its component GameManagerBehavior
and save it for the future.
Note : you can do this by setting the field in the Unity editor or by adding a static method toGameManager
returning a singleton instance from which we can getGameManagerBehavior
.However, in the block of code shown above, there is a dark horse: a method
Find
that works slower during the execution of an application; but it is convenient and can be used in moderation.
Take my money!
We are not subtracting gold yet, so we 'll add this line twice OnMouseUp()
, replacing each of the comments // TODO: вычитать золото
:
gameManager.Gold -= monster.GetComponent<MonsterData>().CurrentLevel.cost;
Save the file and return to Unity, upgrade a few monsters, and look at updating the Gold value. Now we subtract gold, but players can build monsters as long as they have enough space; they just borrow money.
Infinite credit? Fine! But we cannot allow it. The player must be able to put monsters, as long as he has enough gold.
Gold check for monsters
Switch to IDE with PlaceMonster.cs and replace the contents with the CanPlaceMonster()
following:
int cost = monsterPrefab.GetComponent<MonsterData>().levels[ 0 ].cost; return monster == null && gameManager.Gold >= cost;
Get the placement price of the monster levels
in it MonsterData
. Then we check what is monster
not equal null
and what is gameManager.Gold
greater than this price.
The task for you is to add yourself to CanUpgradeMonster()
check whether the player has enough gold.
Solution inside
Replace the line:
return true ;
on this:
return gameManager.Gold >= nextLevel.cost;
Она будет проверять, больше ли у игрока Gold, чем цена апгрейда.
Save and run the scene in Unity. Now try-how to add unlimited monsters!
Now we can build only a limited number of monsters.
Tower politics: enemies, waves and waypoints
It is time to "pave the way" to our enemies. Enemies appear on the first point of the route, move to the next and repeat the process until they reach the cookie.
You can make the enemies move like this:
- Ask the way for the enemies to walk
- Move the enemy along the way
- Turn the enemy so that he looks forward
Creating a road from route points
Right-click on the Hierarchy and select Create Empty to create a new empty game object. Call it Road and position it at (0, 0, 0) .
Now right-click on the Road in Hierarchy and create another empty game object as a child of the Road. Call it Waypoint0 and place it at (-12, 2, 0) - from here the enemies will begin their movement.
Similarly, create five more route points with the following names and positions:
- Waypoint1: (X: 7, Y: 2, Z: 0)
- Waypoint2: (X: 7, Y: -1, Z: 0)
- Waypoint3: (X: -7.3, Y: -1, Z: 0)
- Waypoint4: (X: -7.3, Y: -4.5, Z: 0)
- Waypoint5: (X: 7, Y: -4.5, Z: 0)
The screenshot below shows the route points and the resulting path.
Making enemies
Now create several enemies so that they can move along the road. In the Prefabs folder there is an Enemy prefab . Its position is (-20, 0, 0) , so new instances will be created off-screen.
In all other respects, it is configured in much the same way as the Monster prefab, has a AudioSource
child Sprite
, and we will be able to rotate this sprite in the future without turning the health bar.
Move the enemies along the way
Add a new C # script called MoveEnemy to the Prefabs \ Enemy prefab . Open the script in the IDE and add the following variables:
[ HideInInspector ] public GameObject[] waypoints; private int currentWaypoint = 0 ; private float lastWaypointSwitchTime; public float speed = 1.0f ;
A waypoints
copy of route points is stored in the array, and the string above ensures that we cannot accidentally change this field in the Inspector , but still will have access to it from other scripts. keeps track of where the enemy is heading from at the current time, and the time is stored when the enemy has passed through it. In addition, we keep the speed of the enemy. Add this line to :[HideIninspector]
waypoints
currentWaypoint
lastWaypointSwitchTime
speed
Start()
lastWaypointSwitchTime = Time.time;
So we initialize lastWaypointSwitchTime
with the current time value.
To make the enemy move along the route, add to the Update()
following code:
// 1 Vector3 startPosition = waypoints [currentWaypoint].transform.position; Vector3 endPosition = waypoints [currentWaypoint + 1 ].transform.position; // 2 float pathLength = Vector3.Distance (startPosition, endPosition); float totalTimeForPath = pathLength / speed; float currentTimeOnPath = Time.time - lastWaypointSwitchTime; gameObject.transform.position = Vector2.Lerp (startPosition, endPosition, currentTimeOnPath / totalTimeForPath); // 3 if (gameObject.transform.position.Equals(endPosition)) { if (currentWaypoint < waypoints.Length - 2 ) { // 3.a currentWaypoint++; lastWaypointSwitchTime = Time.time; // TODO: поворачиваться в направлении движения } else { // 3.b Destroy(gameObject); AudioSource audioSource = gameObject.GetComponent<AudioSource>(); AudioSource.PlayClipAtPoint(audioSource.clip, transform.position); // TODO: вычитать здоровье } }
Let's sort the code step by step:
- From the array of route points we get the starting and ending positions of the current route segment.
- We calculate the time required to travel the entire distance using the formula time = distance / speed , and then we determine the current time on the route. With help
Vector2.Lerp
, we interpolate the current position of the enemy between the initial and final exact segment. - Check whether the enemy has reached
endPosition
. If yes, then we process two possible scenarios:
- The enemy has not yet reached the last point of the route, so we increase the value
currentWaypoint
and updatelastWaypointSwitchTime
. Later we will add a code to rotate the enemy so that he looks in the direction of his movement. - The enemy has reached the last point of the route, then we destroy it and launch the sound effect. Later we will add a
health
player reduction code .
- The enemy has not yet reached the last point of the route, so we increase the value
Save the file and return to Unity.
We inform the enemy the direction of movement
In their current state, the enemies do not know the order of the route points.
Select Road in the Hierarchy and add a new C # script called SpawnEnemy . Open it in the IDE and add the following variable:
public GameObject[] waypoints;
We will use waypoints
to store links to the route point in the scene in the correct order.
Save the file and return to Unity. Select Road in the Hierarchy and set the Size of the Waypoints array to 6 .
Drag each of the Road children into the fields by inserting Waypoint0 into Element 0 , Waypoint1 into Element 1, and so on.
Now we have an array containing the route points in the correct order - note, the enemies never retreat, they persistently strive for a sweet reward.
Checking how it all works.
Open the SpawnEnemy IDE and add the following variable:
public GameObject testEnemyPrefab;
It will store a reference to the prefab Enemy in testEnemyPrefab
.
To create an enemy when the script starts, add to the Start()
following code:
Instantiate(testEnemyPrefab).GetComponent<MoveEnemy>().waypoints = waypoints;
So we will create a new copy of the prefab stored in testEnemy
, and assign it a route.
Save the file and return to Unity. Select the Hierarchy object Road and select the option Test Enemy Prefab Enemy .
Run the project and see how the enemy is moving along the road (in GIF for greater clarity, the speed is increased by 20 times).
Noticed that he does not always look where he goes? It's funny, but we're trying to make a professional game. Therefore, in the second part of the tutorial we will teach the enemies to look ahead.
Where to go next?
We have already done a lot and are moving quickly to create our own game in the tower defense genre.
Players can create a limited number of monsters, and the enemy is running along the road, heading for our cookie. Players have gold and they can upgrade monsters.
Download the finished result from here .
In the second part, we consider the creation of huge waves of enemies and their destruction. See you!
How To Create A Tower Defense Game In Unity
Source: https://sudonull.com/post/15228-Creating-a-Tower-Defense-Game-in-Unity-Part-1
Posted by: rivasasaing.blogspot.com
0 Response to "How To Create A Tower Defense Game In Unity"
Post a Comment