I do Technical Evangelism stuff at Microsoft Greece. Please visit new and updated blog at www.dgkanatsios.com
Please check the new and updated blog at http://www.dgkanatsios.comCurrent blog post can be found at http://dgkanatsios.com/2014/09/04/a-tower-defense-game-in-unity-part-1-3/
If you’re in a hurry, you can find the complete source code on GitHub: https://github.com/dgkanatsios/TowerDefense and the second part of the tutorial is posted here: http://studentguru.gr/b/dt008/archive/2014/09/06/a-tower-defense-game-in-unity-part-2
Unless you’ve been living in a cave in the recent years, you surely must have played a Tower Defense style game. Be it Plants vs Zombies, Kingdom Rush, geoDefense, Jelly Defense or any other, I’m sure you’ve spent much quality time setting up your defenses, killing enemies and advancing stages. So, since it’s one of the most common game types you can find on the application stores out there, I decided to try my luck and create one from scratch, using Unity3D and present it in this tutorial. Since it’s a bit bigger compared to my previous efforts, it will be split in two parts. In this first post, we’ll describe the game, the level editor, the respective XML creation and parsing and the object pool used in the game to increase performance. Check the screenshot below to get a taste of how the game looks like, running on the Unity editor.
Scenario and gameplay are both pretty basic, actually. Badgers are attacking user’s bunny house and she has her Chuck Norris trained bunnies to protect it, by shooting arrows. In order to be able to create more protector bunnies, user needs carrot money. If user kills all the badgers after the predetermined number of rounds, she wins. If enough badgers get into the bunny house, user loses! Badgers follow a path in order to approach the house upon which protector bunnies cannot be placed.
Let’s dive a bit deeper into the game mechanics and gameplay.
- Bunny House: Initial life is 10, each badger that arrives If 10 badgers arrive at the house, game is over.
- Path: The path that the badgers will walk on in order to arrive at the house. Specific waypoints will designate their direction.
- Badger: Our enemy. It has a speed property and a health component, which is decreased when it is hit by an arrow. It follows waypoints (non-visible gameobjects) that are placed on the path pieces.
- Bunny: Our defense. It can shoot arrows at a constant fire rate. It starts its activity by looking for an enemy at a close vicinity. If it finds one, it starts shooting. If the enemy dies or leaves the vicinity, it searches for another enemy. Has a standard carrot cost to create.
- Carrot: Falling randomly from the top of the screen. User needs to tap/click on them in order to increase money, to create more bunnies.
- BunnyGenerator: (yeah, I could have come up with a better name) It’s the bunny on the lower left part of the screen. User needs to drag it in order to create new bunnies. On areas that a new bunny cannot be created (such as on the path), it will highlight them as red. Upon a new bunny creation, a standard amount of money will be taken of the user’s account.
- Level Generator: All game levels are to be stored in an XML file that uses a standard format. The Unity developer has the option to use a custom made Unity editor that saves the file from the scene editor to an XML file.
We also have a couple of “roles” that will be mentioned in this tutorial
- Unity developer: The one that will use our custom editor to create new levels for the game
- User/gamer: The end user that will enjoy our game!
As we mentioned, all game levels are stored in an XML file. When we design something like this, we first need to determine the stuff that will be placed into it. Take a look at the Level1.xml file contents, located in Resources folder (in order to be able to pull its into during runtime).
From the above picture, you can see that we store the initial money of the user, two variables to modify the spawn time for the carrots, the location of the bunny house (thereafter “Tower”), X and Y location for the PathPieces (the path that our enemies will walk on), X and Y location for the waypoints (that our enemies will follow in order to get to the bunny house) and a minor piece of information for each game Round. Specifically, PathPieces contain information about the level path sprites and Waypoints are to be used for empty GameObjects creation that will “guide” the enemies to the Tower. The carrot spawn time will be randomly chosen each time and it will be clamped between the minimum and maximum values we set. Before we dive into the code, let's mention that game code was written in Visual Studio (check the free Community edition here). For debugging purposes, don't forget to check the Visual Studio tools for Unity here.
All this information is mapped to the LevelStuffFromXML class
We use the ReadXMLFile method to obtain information from the XML file at runtime. We could use serialization for the class but we instead chose LINQ to XML because 1. it rulezzz 2. its syntax is gorgeous! and 3. it allows for greater control of how your object stuff/properties are converted to XML and vice versa. Consequently, we use the XDocument API to parse the Level1.xml file in the resources folder and create a LevelStuffFromXML instance, to be used in our game. Code used is pretty straightforward and self-explainable but, nevertheless, we use a top-down approach starting from the root XML element and traversing the tree to the bottom, fetching specific attributes and xml elements along the way. Should be pretty clear for you, even if you’re a beginner!
One may ask, why save on an XML file and not save each level into its own scene? Well, this could also be done. However, we opted to use this method for the following reasons
So, you might ask, does one have to write the XML file by herself? Well, one could do that! However, we wanted to provide a convenient way for the Unity developer to create the XML file, so we created a small custom Unity editor window. If you open the project on Unity, you will see a new menu item titled “Custom editor” with a single command named “Export level”.
If you click on that, you will be presented with the below window
By clicking on the “Add new round” button, you can create a new round that has specific number of enemies, based on the value of the below slider. Try it! After a few rounds’ addition, result can be like this
That’s the way to create the rounds for our level. Of course, you can delete a round, if you did a mistake. Moreover, you can also modify values for initial money, the carrot spawn times and the desired filename for the exported level. If you try and export the level in an empty scene, you’ll be presented with the below error message.
Dear friends, every user input is evil!!! You should always protect user input stuff in apps *and* games for bad input. In our case, we do not have a tower object, so the export script would create an well-formed xml file for the game (syntactically correct) but it would not be proper, since we absolutely need a tower object for each level (logically incorrect). Let’s now see the code in depth.
First of all, the script is located on folder called Editor. This is a folder recognized by Unity (via name convention) to contain editor related code.
The corresponding class is not a MonoBehavior, like the ones we’ve been building so far. It’s a special type of class, called EditorWindow, with a static method ShowWindow and a corresponding C# attribute, to designate the menu item to be created for this purpose. We also have some variables to help with our editor window creation.
The OnGUI method helps with the appearance of the necessary GUI elements.
We use a scrollview (via the EditorGUILayout.BeginScrollView and EditorGUILayout.EndScrollView methods) to host the number of the rounds that the Unity developer will create (since she may create a lot of rounds). For each round, we display a label (mentioning the round number), the number of enemies for this round and a button to delete the round. We then continue by displaying an IntSlider that will used to designate how many enemies we want, plus a button to add a new round with the required number of enemies. We finish up the creation of our GUI by creating some other IntSliders that accept the initial money amount and the carrot spawn time (minimum and maximum). Finally, we have a button that will call the Export method that will save our new level, if the Unity developer has created it properly.
At the first part of the Export method, we create a new XDocument and add the root Element (called “Elements”). We begin by adding the XElements for the Paths (with their corresponding X and Y attributes). We validate the Waypoints via the WaypointsAreValid method and then we add them, in a similar to paths way.
At the second part of the Export method, we add all the rounds the user has created, along with their corresponding number of enemies. We also add the tower, the initial money and the carrot spawn time. If a gameobject tagged as “Tower” cannot be found in our scene, we display an error message and exit the method. Moreover, before saving, we use some validation logic (shown below) and we display a final confirmation for the user. If the Unity developer opts to save her work, we save the file into the Assets folder.
Regarding the waypoints, when the Unity developer places them on the scene, she must a) tag them as “Waypoint” and b) add a OrderedWaypointForEditor component. These components have an Order field, which represents the order in which our enemies will walk towards our waypoints. The waypoint with the lowest order will be our entry point whereas the waypoint with the largest order must be located at the bunny house. The WayPointsAreValid method checks all waypoints to determine whether a) all of them have a OrderedWaypointForEditor component and b) all their order fields are different. If both of these conditions are satisfied, the method returns true. Otherwise, it informs the user of the error and returns false.
The InputIsValid method checks the various variables for invalid input and returns false if it is not valid. The ShowErrorForNull method will display an error dialog if the user has forgotten to add a specific gameobject.
That’s all for our EditorWindow! So, you could ask, how does one make a new level?
Let’s see the steps to create a new level for our game!
1. Create a new empty scene, set the Camera to 0,0,-10, Orthographic projection and 0.3 and 1000 for the Clipping planes. Make sure it’s tagged as “MainCamera”.
2. Drag a Tower prefab (located in the Prefabs folder) to the scene
3. Drag some paths to, well, make a path! Useful keys are Ctrl-D (for gameObject duplication) and V key for vertex snapping (to make gameObjects align to each other). You should now have something like this
4. Create empty gameobjects to represent our waypoints. For better results, create one outside of the path (the lowest one on the below screenshot), one at the first part of the path, one at each corner and one at the bunny house. Add the OrderedWaypointForEditor script to all of them and modify their Order field respectively. Do not forget to tag them as Waypoints!!!
5. Open the Custom Editor –> Export Level menu item
6. Add some rounds, edit whatever you want and add Level2.xml as the filename
7. Click Export and agree, go to the Assets folder and right click –> refresh. Level2 file will appear. Drag it to the Resources folder.
8. Go to the Utilities.cs file (located in the Scripts folder), find the ReadXMLFile method and change the “Level1” to “Level2” (yeah, I should have parameterized that somehow).
9. Go to your new scene and delete everything, apart from the camera. Yeah, you won’t regret it!
10. Drag the RootGO prefab onto the scene. Place it at 0,0,0 if placed elsewhere.
11. Ready??? Press the editor’s “Play” button and your new level will appear! Congrats, you should feel great and powerful!
Another thing worth mentioning is the use of object pooling for some of the game objects on this game. If you are new to this topic, we’d encourage you to view this video tutorial from Unity. In short, object pooling helps from a performance perspective. In other words, lots of times you want to add objects to your game that are short-lived. This would imply the following flow
- Create the object (either via “new GameObject” or “Instantiate”)
- object lives its lifetime
- Destroy() gets called on the object, when it is no longer needed
However, Instantiate has a performance hit and Destroy does not immediately remove the object from memory (this depends on the Garbage Collector lifecycle). Object pooling, on the other hand, works like this
- Calculate how many objects will be needed (e.g. we suspect that our game will not have more than 10 arrows “alive” at the same time)
- Instantiate those objects and set them as inactive
- Each time the game needs an object, it fetches the first inactive from the list
- Object is set to active and lives its lifetime
- When the object is no longer needed, it is set to inactive
That’s a pretty basic explanation of object pooling, onto the source code now!
Our ObjectPooler class begins by declaring some variables. Parent and PooledObject are optional, if you want to set a parent for the newly created object or instantiate it via a prefab, respectively. We also have a PoolLength variable and an array that contains any components we want our newly created gameobjects to have. The Initialize method is overloaded, in case you want the pooled objects to have any components. The PooledObject list contains all the created gameobjects.
The CreateObjectInPool method creates a new gameobject (either instantiating the PooledObject prefab or calling GameObject’s constructor) and optionally adds any components (via AddComponent(Type type) method) and parent. Most importantly, it sets the gameobject as inactive and adds it to the list.
The GetPooledObject method is the method that will be called from external scripts. It tries to find a gameobject in the PooledObjects list that is inactive (i.e. ready for use). If it finds one, it returns it. Otherwise, it will double the list’s capacity, create gameobjects in it and will return the first one that we created.
That’s it for the ObjectPooler class, let’s now take a peek at the class that it is using it.
This is the object that will hold references to our ObjectPoolers. For this tutorial’s purposes, we’ll use object poolers for the arrows shot by the bunnies and for our audio objects.
This class follows the singleton pattern, since we want only one instance of it to be available at any given time. We have two references for our poolers (Arrow and Audio) and, upon the time Start() is called, we initialize the poolers. Arrow is a prefab so we have a field for that, to be filled at the Unity editor. The AudioPooler is initialized with an AudioSource type (for an AudioSource component to be created) whereas the AudioPooler gets the Arrow prefab.
The second part of this tutorial can be found here: http://studentguru.gr/b/dt008/archive/2014/09/06/a-tower-defense-game-in-unity-part-2
You can find the complete source code on GitHub: https://github.com/dgkanatsios/TowerDefense
I like the XML approach to generate the levels, thanks!
Open the Custom Editor –> Export Level menu item
error "you cannot have 0 waypoints"
Have you tagged your waypoints as "Waypoint"?
Ok thank you, it has not been given the tagged waypoints. :D
Hi, if i got different XML File want to read for different level,
how and what should i change in the Utilities and GameManager Scripts Files?
You should modify the LINQ To XML code to properly read your XML file. Try searching the MSDN library for relevant LINQ to XML samples
how i can change distance to shot enemy
Powered by Zimbra