Hello and welcome to this blog post. Today I am happy to present a game me and a couple of friends have been working on. In this post I will be talking a little bit about what the game is and where you are able to view/download it, as well as some more intricate details behind some of the systems I worked on within the game. Follow the links below to be taken to the section of the blog that you wish to read.
Introducing: TD Town
Myself and a couple of friends, Paul Gush and Sam Crosby have been working hard to create a pixel art stylised tower defence game, which we have called TD Town. Below is a short trailer Sam put together for us.
If you are versed in a wide manner of gaming genres you might already know what a tower defence game entails, but for those that aren't entirely sure, below is a brief explanation of the genres core gameplay features.
In a tower defence game, the player is tasked with defending a high priority target. Most of the time this is a structure or strategic location, depending on the scenario of the game, but this could be a multitude of things. For our game, we have decided to situate our game within a small town with a power plant that an invading force has deemed a necessity to demolish to help with their war efforts.
The game is played on a wave based system, increasing in difficulty the higher wave you reach. The enemies will follow the designated path towards the power plant whilst trying to survive from the players defences. To defend the power plant, the player will have limited access to towers and a small amount of coins to spend on tower construction and upgrades, earning more coins for each enemy defeated along the path. Eventually the player will unlock more towers, based on the wave they are on. For our game, reaching higher waves allows the player to access stronger towers, such as the sniper tower or laser tower, allowing them to upgrade from the basic pistol towers they have access to at the start.
The game's wave system is endless, meaning the enemies will continue to grow in numbers and strength until the player is unable to defend from the horde of enemies descending onto the plant, however, the player is scored based on their successfulness in defending the plant and saved as a high score, challenging the player to improve and beat their previous runs.
Where to find TD Town
We are proud to announce that our game is available for free on itch.io. By clicking the logo below, you will be taken to the page from which you can download the game.
Creating the Game
In this section I will talk about the features that I worked on within the games development, mainly about the wave and enemy management systems that were implemented. The other members of the team have created blog posts talking about specific implementations of features that I may mention throughout this explanation. I will provide links to these as they are brought up which you can follow if you would like to read about them.
To create TD Town we used the Unity Engine as our development environment as we are all comfortable with the software and confident about our skills within the engine to produce the product that we envisioned.
Enemy Management System
For one of the main features of any tower defence game, the enemies and management of them is crucial for making a fluid game. For our game's data storage system of the enemies, we implemented them as Scriptable Objects, which allowed us to define specific data that all iterations of a specific unit would use. This also helped with game balancing and data changes in the later stages of development.
Now, for the base of the enemy management system, we needed to make sure there were a few main features that the enemies were capable of; Respawning, Dying, Movement and Dealing Damage.
To make things easier on ourselves, we implemented a basic form of movement management in the form of checkpoints. How we created this, was set up a collection of empty Game Objects in unity's hierarchy and placed them at each corner of the path we had created on a tilemap in order from the start of the path to the end goal (the power plant). What the enemy would do is, based on their current checkpoint number, this being zero on respawning, they would move towards the next checkpoint in the list and on reaching that, update their current checkpoint and repeat the process.
In the above code, as mentioned previously, all that happens is that when the distance between the position of the enemy and their target checkpoint is less than 0.1 then they are moved onto the checkpoint to make the animations and movement work fluidly, and then the units current checkpoint target is updated to the next checkpoint.
For the actual movement of the units, the same principle is used but in reverse, we continuously checked the distance between the unit and their target checkpoint within a while loop and if the distance returned greater than 0.1 in size, then the unit would move towards their target. Other parameters are used in the movements while loop to ensure that the unit interacted correctly with other features of the game such as time acceleration and the game being paused but these features will not be covered here.
After getting the enemy units to move and follow a path, the next focus was on getting them to interact with the end goal (the power plant) as another member of the team was still working on the tower implementation meaning we wouldn't be able to test any damage implementation properly. The two main features that we wanted to add here was the condition of reaching the plant as well as a respawn mechanic.
We started with the OnReachGoal() method that would control what would happen if the unit reached the power plant. This was simply a case of deciding what needed to be done, in our case, we told the unit to do 3 simple things; tag themselves as "dying", damage the integrity of the power plant (reduce the 'home base' health) and reset to an Object Pool.
We needed to tag the enemy as dying upon reaching the power plant as the enemy could potentially be killed by a nearby tower after it reached the goal meaning the wave controller behaved incorrectly and started the next waves early or processed logic multiple times meaning the base instantly exploded when only one unit reached the end.
Another two features that the enemies needed to be capable of was to respawn next wave or for a new unit in the current wave after resetting to an object pool and to take damage. These two features were not as complicated as they may seem. The resetting to the object pool was simply a case of changing the units parent object in the scenes hierarchy to the object pool, resetting its local position to match the object pools so the unit is off-screen and then changing a few variables that managed the wave management logic and the units current checkpoint target. The respawning of the unit was just a case of tagging the unit as alive, un-parenting the unit from the object pool, resetting health values and then starting its movement logic.
Lastly, the unit would need to be able to take damage from incoming tower fire, otherwise the unit would be immortal and the player would never advance past a certain amount of units, no matter how hard they tried. This was a simple calculation done on the projectile side of the game, using the following method.
After the damage was calculated, it would also check to see if the unit is still alive. If the unit has been killed, then the player is rewarded the coins and points that the unit was worth on kill, then the unit plays a death animation and then resets to the object pool.
Wave Management System
Now, the wave management system is a little more complicated then the enemy management system due to the fact that the wave management system needs to hold logic for the entire wave system and not just an individual units logic.
Firstly, the wave management system needed to be initialised correctly on the game start. This meant assigning the correct values to variables, populating the dictionaries used to determine the current wave status, and finally starting the game by spawning the correct units for the wave.
To store the data for the waves contents and to check the progress of the current wave, 3 dictionaries were used.
After the initialisation of the variables and dictionaries, the bulk of the wave management system is activated. This consists of two main methods that are used to spawn the correct amount of each type of unit required for the current wave. There are other smaller methods used to assist in performing this logic but I will not be covering these.
Spawn Wave Logic
The logic for spawning the wave encapsulates the logic for spawning a single unit as well. Working together to ensure the correct number of units are spawned for the wave.
To start off the spawn wave method, we needed to reset a couple of simple variables that are used to check the progress of the wave. If these weren't correctly reset, the wave manager would only spawn the difference in the new waves unit count and the previous waves unit count meaning the waves never increased in size and sometimes decreased in size depending on the wave.
After setting the variables, the wave progress checking method was started, ensuring that if an enemy was killed quickly, the logic of the spawn wave method and the actual wave progression logic wasn't thrown an error. Next, a few UI updates are handled, which tells the player how many units are left, and what type and amount of units are included in the new wave. Paul was the lead developer on the User Interfaces side of the game, on which you can read about here.
Once all that is complete, the main unit spawning loop is started, only running up until the amount of units spawned is less than the total amount of units desired for the wave.
How this works is that two of the dictionaries initialised at the start of the game, which are updated each wave, are compared. One of these dictionaries is the standard (WaveUnitDict), and the other is the current (SpawnedUnits). The standard dictionary means that the values are only changed on wave progression (completing the wave) and the current dictionary's values are changed within the spawn loop. The keys in both dictionaries are the same, as they are the units of the game, so we are only looking at the value part of the KeyValuePairs<> that the dictionaries are comprised of.
If the wave is still in need of units to be spawned, the dictionaries KeyValuePairs<> are compared. We check each unit that can be spawned in the game in the standard dictionary and compare its value to the current dictionary's value of the same unit (key). If the current's value is less than the standard's, that tells us that a unit of this type is still required this wave and then spawns the unit. This will loop until there are no more units needed for the current wave.
Spawn Unit Logic
Now that the spawn wave logic needs to spawn a unit, the method that handles everything to do with individual unit spawning is called.
This is when the object pool for the enemies is retrieved from for each of the units that need to be spawned. Firstly, the enemy pool is checked to see if there is a game object currently available to assign the "spawned" unit to. This game object will be a prefab of an enemy unit that holds all the components needed as well as the enemy management system script we created previously to perform the necessary logic on to the enemy.
If there is not a viable game object for our unit, the spawn system won't calculate any of the unit spawn logic, and for development purposes, produced a console debug message stating that the unit failed to spawn. This will not affect the wave spawn logic in any way, all that will happen is it will just skip unit spawn calls until there is a viable game object available to use for the next unit. This produced a non-linear wave pattern in the later levels which produced a unique challenge to the players.
If there is a viable game object, however, then some simple assigning of variables and scriptable objects are carried out, before updating the wave spawning logic with the updated spawn value of the unit and increment the spawned unit count for the wave. Finally, the unit would be set to active, enabling the enemy management system to take over control of the unit before adding a brief pause in logic computation to allow space between units being spawned.
Wave Progression Logic
Finally, the wave management system is in control of checking the wave progression. The way in which this was implemented was a series of checks to see if the player had fulfilled the required goal, or if the enemies has succeeded in their goal of destroying the power plant.
Firstly, the overall arching check was to see if the player had failed at defending the power plant, if they were unsuccessful, then it was game over. If they were still defending the plant, the wave management system needed to check to see if the total amount of units for the wave had been spawned. Whenever the total amount of spawned units for the wave was indeed at the amount of units designated for that wave, the next check was to see if there were any enemies alive. Once there were no more units left to kill, the game would then mark the wave as complete, increment the wave number by 1, wait for 2 seconds giving players a very brief respite from the enemies allowing them to build/upgrade towers before starting the next wave.
Improvements for a future Game/Update
Taking what we have created in terms of an enemy management system for this game, I think that improving this system with a proper pathfinding logic would help improve the quality of the game, as well as giving us creative freedom when it comes to level creation and design. Due to the method of our checkpoint based pathfinding it meant that the path was hardwired into the wave management system for the units to follow, removing the option to create multiple levels easily for the game. I feel that using a pathfinding logic such as A* would remove this limitation as the pathfinding logic could work based on level data rather than pre-determined, in scene checkpoint game object's positions.
That is it from me. Thank you for taking the time to read this post and I hope you have found this interesting and are intrigued about the game we have created. As always, if you would like to stay up to date with any projects that I am currently a part of and working on, be sure to check back regularly to see if there is any news or updates!
Comments