Tower Defence Game - 3 weeks
Check out the Tower Defence games made previously in this course:
Compilation of previous tower defence games
Watch the lab Video
This lab is split up over three weeks.
- Lab 6 we will set up most of the basic game mechanics and learn about tile based levels.
- Lab 7 we will focus on creating a functional UI and building mechanics.
- Lab 8 we learn about creating content and balancing the game.
By the end of the three weeks, you should have a minimal functional tower defense game. Should you find the flow too easy or you wish to challenge yourself more, then you are strongly encouraged to try out completing the extra tasks as well. Many of the techniques learned here will also be applicable to RTS games, as TD is very much a simplified RTS subgenre.
Lab 6
All tasks should be completed before the end of lab 7. The work has to be submitted after all 3 practices are completed.
Step 1. Setting up the project
- Create a new 2D Unity project
- First, we are going to need tiles. You can make your own tileset, find one, or use one of the following recommended ones:
- Create a graphics folder and import the tiles from the asset pack.
- Select all the images and set the pixels per unit to match the edge of the tile in pixels. For recommended packs, this is 64.
Step 2. Creating the first level.
We are going to use a new Unity feature called tilemaps for this. The usage of this will be presented in practice. Should you be unable to attend, then you check out the manual or tutorial videos on how to use this.
Unity live training on tilemaps:
- https://unity3d.com/learn/tutorials/topics/2d-game-creation/core-tilemap-concepts?playlist=17093
- There are many great tutorials from content creators on Youtube for this as well.
Once you are finished with the level layout, your map should have:
- A spawn structure where the opponents will appear at. One is required minimally, but you can have more if you wish.
- A base structure the player has to defend.
- A long winding road between the spawn points and the target.
Step 3. Making things move.
For the level itself we used tile based graphics, but that does not mean that our movement logic has to do that. Instead, we will take a more simplified approach that you can also apply to non tile based levels. Our goal is to create two scripts:
- A Waypoint script that we can use to create a path.
- A WaypointFollower script that can make gameobjects move along a path of waypoints.
Waypoint
- Create a new empty gameobject and call it Waypoint
- Create and add a new script called Waypoint.
- In Inspector window set an icon for the gameobject. Click on the thing to the left of the name to open the icon selection box.
- Turn the gameobject into a prefab.
- Add GetNextWaypoint method to the waypoint script. The return type should be waypoint.
- Add a public variable Next of the type Waypoint to the script.
- Extra: Turn the public variable into a list of waypoints to make it possible for enemies to split to multiple roads.
- Copy the code down below to your script to better visualize the path. The variable next should match with the name you used in step 6.
- Place the waypoint prefabs to your scene to create a path from spawn to the goal.
- NB: Always keep your scene hierarchy organized! Add a common parent object for all the waypoints.
void OnDrawGizmos() { if (Next == null) return; Gizmos.color = Color.yellow; Gizmos.DrawLine(transform.position, Next.transform.position); }
WaypointFollower
- Pick a character sprite from the assets and drag it into the scene.
- Fix the name of the gameobject.
- If the sprite isn't properly visible on your terrain then fix the layering order.
- Add a RigidBody2D component and set its type to kinematic.
- Create and add a WaypointFollower script.
- Turn the gameobject into a prefab.
- Implement the WaypointFollower script:
- Add a public Waypoint field for the follower to move towards.
- Add a public float field speed to set movement speed.
- In the Update method, move the game object towards the waypoint.
- In the Update method, if you reach a waypoint, then set a new one using the GetNextWaypoint method.
- Add error checking, so the code does not throw exceptions when the waypoint is missing.
- Extra: Also change the rotation of your waypoint follower when a waypoint is reached. This is only applicable to top-down sprites!
- Add a public static int counter to the script to keep track of how many opponents there are in the game. Increase this counter in Start() and decrease it in OnDestroy()
- Run your game and test the movement functionality.
Step 4. Spawning enemies
- Select the first waypoint at the start of the path.
- Create and add a new Spawn script to it.
- Rename the GameObject to Spawn.
- Break the gameobjects link to the current prefab. (GameObject>Unpack Prefab)
- Turn the gameobject into a new prefab.
- Modify the Spawn script:
- Add a public field for WaypointFollower prefab.
- Add public fields for configuration: the number of opponents per wave, time between spawns in a wave, time between waves.
- Use private fields to keep track of current spawning status.
- In the Update instantiate new enemies until all the enemies for the current wave are spawned.
- When instantiating an enemy, set the waypoint to the one attached to the spawn gameobject itself.
- Create a public method StartNextWave. Use this to reset all necessary variables.
- Run the game test it. At this stage, all enemies of a single wave should be spawned and piled up on the last waypoint.
Step 5. Removing enemies
- Select the last waypoint and name it Base. This will be the object attackers are trying to get to. You can also use a more descriptive name based on your game design or assets.
- Break the current waypoint prefab instance and create a new one.
- Add a CircleCollider2D component and set it as trigger.
- Add a CircleCollider2D component to the enemy prefab. (Waypoint followers)
- Add a new Base script.
- Implement the OnTriggerEnter2D method.
private void OnTriggerEnter2D(Collider2D collision) { // Check if the collision is with a waypoint follower // Destroy the waypoint follower // This is also a good place to update the score, but this is a topic for the next lab. }
Step 6. Defending with towers.
We will skip the building mechanic for the moment and return to it in the next session. The goal of this session is to have working towers that attack enemies. While the player is not able to build them just yet, you should place them in the scene manually and verify that they do indeed work.
Making units attackable
- Create a new script named Health.
- Add the script to WaypointFollower prefab.
- Add variables to be able to configure the starting health of the unit from the editor and to keep track of the current hp.
- Add a public Damage method to damage the health value.
- Destroy the gameobject when health reaches 0.
Targeting enemies
- Create a new tower prefab. It should have a sprite and a proper name.
- Add a CircleCollider2D component.
- Set it as a trigger. This will be the attacking range of the tower. Modify the size accordingly.
- Add a Tower script and modify it:
- Add a public list of type Health to keep track of enemies in range.
- Add the methods OnTriggerEnter2D and OnTriggerExit2D to keep track of enemies that are in range.
- Test the tower. Any targets currently in range of the tower have to be showing up in the public list visible from the editor.
Next, we need to create a firing method, but before we can get to that, we will need to create something to fire with first. This brings us to projectiles.
Projectiles
- Create a projectile prefab.
- Add a new script Projectile to the prefab:
- Create public fields for the damage and speed values of this projectile.
- Create a public transform field for the target of this projectile.
- In the update method make the projectile move towards the target. This is similar to WaypointFollower done earlier.
- In the update method, destroy the projectile when it reaches the target. Also, inflict any damage it should do to the health script.
Attacking enemies
- Add configurable fields for damage, projectile speed, and cooldown.
- You may also need a private field to keep track of the cooldown later.
- Create a private Fire method that can instantiate a projectile and set its target.
- In update method:
- Check if it is time to attack.
- Pick a target from the targets list.
- Use the Fire method to attack the selected target.
- Add error handling so that there are no null reference exceptions.
- Test your game with towers added to the level.
- This is the end of part 1.