banner



How To Create A Tower Defense Game In Unity

image

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 monsterso 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 monsteris 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?

  1. Unity automatically triggers OnMouseUpwhen a player touches a physical GameObject collider.
  2. When called, this method puts a monster if it CanPlaceMonster()returns true.
  3. We create a monster using the method Instantiatethat creates an instance of a given prefab with the specified position and rotation. In this case, we copy monsterPrefab, set the current position of the GameObject and the absence of rotation to it, transfer the result to GameObjectand save it tomonster
  4. At the end, we call PlayOneShotto play a sound effect attached to a component of an AudioSourceobject.

Now our script PlaceMonstermay 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 MonsterLevelin 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 MonsterDatato store the list MonsterLevel:

                                    public                        List<MonsterLevel> levels;        

Due to the generalizations we can guarantee that Listout levelwill 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 MonsterDataanother variable.

                                    private                        MonsterLevel currentLevel;        

In a private variable, currentLevelwe will store the current monster level.

Now we currentLevelwill set and make it visible for other scripts. Add the following lines to the MonsterDatadeclaration 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:

  1. Set the property of a private variable currentLevel. By setting a property, we can call it like any other variable: either as CurrentLevel(within the class) or as monster.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.
  2. In the getter, we return the value currentLevel.
  3. In the setter, we assign a currentLevelnew value. Then we get the index of the current level. Finally, we cycle through all levels and turn on / off the visual display depending on currentLevelIndex. This is great because when you change the currentLevelsprite 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 in OnEnable, not in OnStart, because we call ordinal methods when creating prefab instances.

OnEnablewill be called immediately when creating a prefab (if the prefab was saved in the enabled state), but OnStartnot 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 MonsterDatafollowing 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 GetNextLevelwe get the index currentLeveland 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 CurrentLevelvalue 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 monsterwith 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 OnMouseUpbranch 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 MonsterDatawith 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 Textused to display the amount of gold the player has.

Now, when he GameManagerknows 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 GameManagerBehaviorfollowing 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 CurrentLevelthat we asked in Monster. First we create a private variable goldto 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 textfor goldLabelto 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 gameManagerto access the component GameManagerBehaviorobject 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 GameManagerBehaviorand save it for the future.

Note : you can do this by setting the field in the Unity editor or by adding a static method to GameManagerreturning a singleton instance from which we can get GameManagerBehavior.

However, in the block of code shown above, there is a dark horse: a method Findthat 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 levelsin it MonsterData. Then we check what is monsternot equal nulland what is gameManager.Goldgreater 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:

  1. Ask the way for the enemies to walk
  2. Move the enemy along the way
  3. 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 AudioSourcechild 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 waypointscopy 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 lastWaypointSwitchTimewith 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:

  1. From the array of route points we get the starting and ending positions of the current route segment.
  2. 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.
  3. Check whether the enemy has reached endPosition. If yes, then we process two possible scenarios:
    1. The enemy has not yet reached the last point of the route, so we increase the value currentWaypointand update lastWaypointSwitchTime. Later we will add a code to rotate the enemy so that he looks in the direction of his movement.
    2. The enemy has reached the last point of the route, then we destroy it and launch the sound effect. Later we will add a healthplayer reduction code .

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 waypointsto 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

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel