So a class at my school, the Art Institute of Orange County, called Advanced Level Design has 11 weeks to create a Tower Defense game. I've already taken the class, but I have a few friends taking it now. They are currently seven weeks in and are only in the blockout stages of the game. I've been asked by a few of these students, and also by a significant amount of people through e-mail about how I would approach building a Tower Defense game in terms of design and implementation with UDK but I don't have any experience in making a Tower Defense game at all. Well thats now going to change.
Taking a note from Dungeon Defense, I thought it'd be nice to document my approach and maybe give some info about how certain things can be implemented. In no way do I claim that this is the 'right' way of doing things. This is a purely experimental project to teach me things about AI, pathing, and etc that I haven't had any experience with yet. In any case, here we go.
Also, I may or may not be live streaming my desktop. http://www.livestream.com/awesomeallar
Day 1 (11/19/2010)
1. The Design Document
Yeaaaaaaaaah I don't really have a design document. I'll design elements as I go. This is a very very bad route I know, but I want to get right into testing out implementation features and building stuff. :D
2. Designing The First Level
Designing the first level for a new game is one of the biggest hurdles that a team can face. The first level is where everything 'hits the fan' for the first time, although definitely not the last time. It is where you realize that your design doc may be lacking in some areas, or simply void of needed information all together. The first level also demonstrates how well the team is coordinated and if the vision for the game was successfully synchronized amongst the team members. While the first level design should be one that is well thought out and discussed to no end, it is also the design which you must be the most accepting to throw away. Rarely does the first level come to fruition in the same way that the vision intends, either due to design flaws, technological barriers, or simply the team just doesn't want to do it anymore. With these snakes and spikes riddling your path on the way to get your first level implemented, one should design the first level in the simplest way possible that will just touch on the designs of the game instead of trying to fully implement every single feature immediately upon game start. Not only will this let you build levels iteratively and incorporate more advanced design implementations later as the pipeline becomes more concrete, it gets people working on small manageable tasks that they can envision done by the end of the week instead by the end of the quarter.The goal here is to design a simple run-of-the-mill Tower Defense level for multiple reasons, with the focus being that once the level is designed and built you can immediately begin testing all of your design elements and see how they work together or if your core game design needs to be rethought, rehashed, and rebuilt. This also has to be done in a timely manner, which means these designs should be **modular**. A modular design lets you keep what works and discard what doesn't without having to fully break apart the puzzle and start again from a deranged mush of elements that no one can figure out how to put together. A modular design lets you take your puzzle apart, but still allow you to retain all of the edge pieces intact so you already have a good start on how to proceed with your next go. To make a modular design, each element must be broken down and compartmentalized, but at the same time they must be able to connect to each other in a way that once interconnected they can then present a game.
I'd also like to note that I installed UDK around 2:00AM on 11/18/2010, developed some of the base ideas of this approach, and have started executing them. Starting with this approach, one should have a fully blocked out level in two days, as opposed to six weeks.
Breaking a Tower defense level down is quite simple. Starting with the basics (and pulling from the current design doc), we need the following things: A base (airship), roads, spawn points, and buildable land. For fun, I will also be throwing in teleport points (which I'll get into later).
If we break these components into smaller pieces, a base is simply a position that enemies are trying to get to. We can also break roads down into two smaller sections, as any true tower defense game will incorporate both land and air units. To have both types of units, you must have roads for both, and thus you get two types of roads: land+air roads and air roads. Air roads are simply just paths that air units can take exclusively during their attacks. With that said, we can now break down spawn points, which now must consist of two types due to there being two types of road: Land+air spawn points and air spawn points. Once spawn points and roads have been established, we now need to break down buildable land. To break this down we must go deep into what can be built, so for now we will just leave this at 'player placeable towers'. My addition, teleport points, will facilitate fast movement from one part of the map to another as being this is a third person game, I'd like to be able to quickly to get to another point in the map without winding through a long long long maze of roads and towers.
Now with these core elements broken down, we need a way to construct a level using these separate but connected elements. Since each design element here really is its own entity and takes up its own space on a tower defense map, we should literally draw a map. Sure, you can hop straight into Unreal and start building but you'll find that you'll have significant scaling issues, confusion as to how things should be built, and etc. Flying around the map files on drop box currently, these issues are ever so apparent, with weird BSP action and awkward player only paths hindering the levels true potential. To be able to construct a level and have it stay true to its design, it must be designed in a way that can easily be built in a straight forward and no-hassle way. Game engines usually have a grid that you can construct your level on to, and seeing as how the elements of this tower design game are their own entities, there is no better option than to design on a grid. If we can take our broken down elements and lay them out on a grid in an easy way to understand, then that will ensure everyone is on the same page as well as making in-game construction incredibly simpler.
Grids are squares. Connected squares. Connected squares that need to represent design elements. What other medium of game development could be more in-line with what we need than connected squares than a board game! Okay, so we don't need to go and fully construct a board game, but there is an aspect in analog game development that readily transfers to the digital game, and that is tile based design. By creating tiles of our design elements and arranging them on a grid/board, we can easily design a level fast and painlessly. So lets create some tiles...
It is important to note that these tiles must represent the game elements such as roads, spawn points, and etc, so we must design the tiles with rules in mind so that they conform to a desired sense of game play. By creating a set of rules along with each tile that denotes where, when, and why it can be placed, we can simultaneously further develop our game design while building our first level. Lets take a look at a Tile Set that I developed for this purpose prior to writing my first post:
I will not write up descriptions of the tiles and their rules here, as they are clearly stated in the above picture. Not that the tiles have very exact rules on how they can be placed and do not leave much room for confusion or error. After I developed the tile set, I didn't immediately start placing tiles. I picked one aspect I wanted to lay out and drew various sketches of that element first. Roads being the key integral part of tower defense, that is what I started with, and I just drew an array of sketches of road paths until I found a simple but elegant design that will be easy to construct and perfect for testing implementation. This was my sketch.
Really simple. A spot for an airship (which I'll be using as the enemy objective and player 'base), and just lines for road paths including two air-only routes. X's marked spawn points, where A's marked air spawns. These aren't conventions I picked, they were just arbitrary letters I picked to represent them as this is only just a sketch. I also named the level Remorse because this sketch kind of looks like a distraught and depressed sad face to me, but that is irrelevant. The symmetry is pretty obvious here, but I chose to implement symmetry for a few reasons: It will be easier to construct as a first level, it will be easier to diagnose game play mechanics, and once again it will be easier to construct as a first level. Being easy to construct is pretty fundamental here, we wouldn't want to build Rome in a day, thats far too much work without guaranteed pay off. One should always keep Cost vs Benefit in their minds as they progress their designs and should check themselves once in a while to be sure they haven't shuffled off the feasibility coil.
With this sketch, I'm ready to build something out of tiles, so the next thing I did was I created a 2048x2048 image with grid lines every 64 pixels for my 64x64 pixel tiles. Then I pasted my sketch into the document and started overlaying square tile layers. My sketch wasn't perfectly symmetric or exactly neat so some parts didn't align to the grid perfectly, but with a tweak here and there I came out with a pretty nice road design for my level set to a grid.
Now I created a set of generic markers in order to represent different things, such as the possibility of expanding into multiple bases or airships. I used a "1" tile to represent my airship. Now that I have these tiles placed, I'm going to look at my tile rules and see what tiles I have to place next. The next tile is the Buildable set of tiles, and these tiles have rules about being placed near spawn points, so by following these rules you are creating a standardized game element that players will be able to pick up if its consistent along with making technical implications simpler by reducing the amount of "what if" scenarios. So here I am playing my buildable tiles...
All my buildable tiles are now placed. Now I have to place player paths and teleport targets (or I can choose not to). In my original sketch, I did not plan for these because I wanted to keep the initial sketch simple, and due to its simplicity I can now plan with more insight as to how these tiles should be placed instead of randomly guessing on a sketch by adding too many features too soon. Here are my player paths and target teleports...
With my desired player paths and target teleports in place, which was easy to figure out if you adhere to the set of rules you establish beforehand, no thinking was required to update the buildable tiles to reflect the player path changes. With this design complete, lets go ahead and put the map name on it so we know what design it is, and with some of that extra space lets be nice and put a compact copy of our tile key on it. This way we can share around the design and just give brief explanations about that the tiles are and have the reader pick up and understand quickly, as opposed to shuffling between the design and a legend repeatedly. Lastly, putting your name on your designs is never a bad thing!
Now with our design... designed, lets build it!
Get the map design .PSD HERE! http://allarsblog.com/CastilloRTS/T_Map_Remorse_D.zip
3. Blocking Out The Level
To begin blocking out our level, we are going to need to build some sort of base floor plane to place our layout on and to build up from it. BSP initially looks like a good option, but BSP can later become a real pain to edit especially when dealing with more complex shapes. We should use something that is a bit more freeform and able to be changed easily. We could use a giant static mesh but then we can't change it all all, however static meshes do overcome the other advantages of BSP. I chose to use a Terrain, as terrain can be edited just by painting and sculpted it, as well as dynamic tessellation and it can be built with a series of inputs to get the exact size we need. Lets go ahead and open up the terrain wizard by going to Tools -> New Terrain.
Seeing as how there are 32 grid squares in my grid I used to design the level, I have chosen to use 32 patches in both directions. Patches are basically the number of grid squares your terrain size will be, not necessarily the size of each square. This might create a terrain without the desired tessellation but it will allow us to have a good place to figure out what we need to change.
After the terrain is created, I immediately go ahead and import my 2048 map design and set the LODGroup to TEXTUREGROUP_Vehicle. The reason for changing the LODGroup is that textures that are bigger than 1024 are generally shrinked to 1024 once in-game unless it is set otherwise. Many use TEXTUREGROUP_Cinematic, but I prefer using Vehicle as I'd like to still maintain exclusive detail control over cinematic props in code later, and this game won't be utilitizing TEXTUREGROUP_Vehicle for any other means.
Now I went ahead and made a terrain layer and assigned a material containing my design as a diffuse as a terrain layer. Placing a player point for reference, I thought the terrain was of an appropriate size but its tesselation was quite terrible and the terrain wouldn't be able to preserve much detail. To fix this, I quadrupled the number of patches of the terrain and then I quartered the size of the terrain. This creates a terrain of 4x more resolution. I then changed Max Tesselation Level to 8 so there are 8 transitions of LOD to make sure we aren't rendering lots of triangles in the distance that we don't need to. However, the LOD distances are still based on the original size of the terrain, so we must multiply the Tesselation Distance Scale by 4 so that our terrain now LOD's accordingly.
With our terrain now properly tesselated and LOD'd, we need to fix the mapping scale of the material so that our texture spans across the entire face. To do this, access the material layer properties in the terrain dialog and change mapping scale from 4 to 128. I kept punching in increasing powers of 2 until I found 128 to be a proper fit.
With this set, it became apparent that my map wasn't aligned to the axis of the world and I wanted the top of my design to be facing +X, so I rotated my terrain mapping by 90.
I then roughly placed a Player Start on the airstrip tile just to get a feel for how the size of the level feels. I then added a dominate directional light so that we don't have to play in unlit mode.
With lighting, we should now build a Lightmass importance volume over our terrain. The Lightmass importance volume tells Unreal and Lightmass which part of the level should be subject to Lightmass calculations. Without a volume, the entire empty world is considered as part of the lighting calculations and significantly slows down lighting build times.
With lighting built, I went ahead and ran around the design a bit. I am satisfied with how it feels and its size dimensions. It might be a tad bit small, but worst case I can just scale down the character.
Now before we build any assets on top of this design, we should at least lay out pathing for the roads. Doing this step now will save time and frustration when trying to place these nodes with everything else cluttering the view. To make sure pathing is as clean and concise as possible to help mitigate AI issues, I place these nodes at positions of multiples of 16. This ensures they are all properly aligned. In the series of pictures you may notice my Z coordinate change a bit, however at the end of the process I decided that 64 is the Z coordinate I will be using. With the aid of the top perspective in ensuring these path nodes are aligned and centered to tiles, I've found that each tile is 256 game units wide. This makes setting up the rest of the path nodes quite easy. To place the rest of the path nodes, I tried cloning and draging with snap to grid enabled but it seems as if the grid does not fully snap smaller entities such as path nodes. I'm not sure if this is a weird behavior with my UDK or the norm, but in any case I decided that it would be faster to use the UDK Layout Tool which can be found here: http://forums.epicgames.com/showthread.php?t=743773
The way the tool works is rather simple, you copy an object in UDK buy selecting it and hitting control C, and then you punch in the appropriate modifies and check the right settings, and then hit generate objects. You can then go back to UDK and hit Paste, which will place the new objects correctly. To begin setting my path nodes, I selected my first path node, hit copy, and set the Layout Tool to translate each copy 256 units +X as a continuous modifier so it is applied to every copy relative to the last one. I used 20 copies as thats how many road tiles were in that direction. I set the group name to Path_Road even though I'm not sure what group names will be used for, but when given the opportunity it is always good to name your things. Then I clicked Generate Objects, and then went back to UDK and hit Paste.
With these path nodes set, I realized that for proper pathing these path nodes should all be pointing in the appropriate direction so that things along the paths will be directed towards the airstrip. Doing this also simplifies future work, as we don't need to worry about calculating the shortest route as the routes directions will be already determined. To fix the rotation, I selected all the path nodes and simply set their rotation:
Repeating the above steps, (and after awhile learning to handle rotation from within the layout tool) I started working on placing all the road path nodes.
When I pasted a set of nodes going in the -Y direction, the perspective camera seemed to show that the nodes were slightly skew from the tiles. I went ahead and forced the highest level of detail of the terrain by setting the Editor Tessellation Level and going into top view to check the wireframe, and sure enough my nodes were perfectly aligned to the grid. Crisis adverted. Also, when I placed these tiles I noticed that there were many hard shadows that were a bit detracting and unpleasant, so I added a skylight at 0.5 brightness to fill the level with some ambient light so the shadows are still present but the design is still clear.
Less than 15 minutes later, I had my paths built.
Now with all the path nodes placed, we need to set a flag for the path nodes that allows them to use their direction for path generation. This flag is called One Way Path, and if you select all of your path nodes by using the Binoculars button at the top of UDK and clicking properties, you can set this property for all of them in one go. With this enabled, pathing will be much more linear for AI.
If we were to build paths right now though, we will still see that pathing connections are being made all over the place as there is nothing to prevent the pathing generation from doing so, so lets start adding in some placeholder assets. These will only serve as temporary assets and will be replaced with custom actors representing their design elements. I am going to start by creating a 256x256x64 tile in 3ds max, unwrap it and give it a lightmap, and then give it a texture of the same tile graphic we used for our design. Set up the material, generate collision with 6-DOP box collision, and you're ready to place these tiles. Because these tiles are made in the size of 256x256x64, they will match our in-game design squares perfectly and can be copied and moved around extremely fast once you set your snap size to 256 (using the [ and ] keys or the drop down arrow in the bottom right of UDK). With this grid snapping, copying and placing these tiles is incredibly fast, and should take less than 10 minutes. The 64 in height should also serve to block pathing nodes for the most part.
Now that we have these tiles placed, lets go ahead and fully build our level to check out how pathing is being handled. Once the level is built, I hid terrain objects, selected all the pathnodes, and shown Paths (T to hide terrain, P to show Paths). This allows you to easily see the path network:
As you can see, our paths are relatively clean for the most part except for a few problem areas. These connections that are being made that shouldn't be made is the result of nothing blocking their path, which the buildable tiles greatly served to do. Before we build more tiles though, lets select all our buildable tiles and assign them to a group so that we can deal with them as a whole if we need to later, for things like replacing or showing/hiding only the buildable mesh group.
I then created a white material to represent an empty tile, duplicated a buildable tile, placed it on an empty space, and overriden its material in its actor properties with the new white material. I then set the group name to Tiles_Empty. With that tile, I then duplicated it placing empty tiles where it will help clean up pathing. There is no need to put an empty tile on every empty square of the terrain, as then you lose the ability to sculpt the dead space of your terrain. There is a shot of the paths built with the new empty tiles:
While some paths were cleaned, a lot of new "purple" paths were introduced as overcome-able obstacles. Lets continue placing the rest of the tiles before we fix this issue in hopes that once all the tiles are placed, this issue will fix itself out. (Note: This is generally a very bad way to develop, but seeing as how it takes at most 10 minutes to place these tiles, these actions are easily reversable and re-doable).
I've placed the air road tiles above the other tiles, this way I can update the building tiles under neath them that correspond to the ground road paths that were obscured with the yellow air road textures. Also, I've created pieces for the spawn points and teleports but instead of being a square tile it is a three walled piece with only one opening to further emphasize the tile rules and show how they are connected to the system. The problem with this is sometimes these pieces can visually clutter up the level as you are working, however if you continued to give your titles group names then you should be able to easily toggle the visibility of these groups while you work.
And also another advantage of groups, if you go to the groups tab of the content browser window, you can click the first object within the group, then holding down shift double click on the last object in the group and UDK will select all the objects in that group.
We have one last tile that we need to worry about pathing for, and those are the player path routes. These routes can't have block tiles on top of them as then the player won't be able to traverse them, but they also need to be cut off by the pathing system for generating the enemy AI routes. To do this, we must create more path nodes but instead of creating usable paths we will be creating 'blocked' paths. We don't need a path node on every tile for this, just ones that are adjacent to travel able paths. Here are some examples of how I set up my blocking nodes.
The paths are still quite a mess with purple (intelligent/high jump paths) connecting nodes that shouldn't be connected together. We want the cleanest paths possible, so in order to fix this we are going to have to change some code. I will not go over how to set up a new code package, this has been rehashed several times on several websites including my own. The class we need to modify is UTGame.UTScout, and normally to modify a stock code class we should derive from it and then change only the properties we need to, but this is one of the very few exceptions where we have to break this rule and modify stock files directly as there is a bug with UDK that causes hardcore crashing if you try to use a custom Scout class. The Scout class is designed to be a basic Pawn with enough logic just to navigate around your map and see if it can get from one node to another, and thus generating pathing information. It controls things on how high you can jump over obstacles to get to a node and etc. Our enemies will not be jumping, so we can restrict jump movement to the point where our nodes can't detect other nodes behind a 64unit high tile and forces us to make clean and precise paths. This is the code I used. Notice how I wrote it in to its own class anyway for reference, so that if UDK ever allows us to have a custom Scout class we can simply switch to it and it will work. The true magic though is taking the default properties listed and overwriting the defaultproperties block in the UTGame.UTScout class, as that is the only code that is allowed to be involved in pathing generation.
With these changes to UTGame.UTScout, lets go ahead and rebuild our pathing. In the screenshot, I have hidden my air road tiles.
Ah, sweet wonderfully clean path connections. Our AI will have no unexpected hiccups now in their travels. (Hopefully) However when we build these clean paths, we still might have a bit of work to do. With a node per tile, this leaves room for path node optimization by removing unneeded nodes. Unreal does a fairly decent job at discovering nodes it doesn't need and will tell you so:
If these nodes are not used for blocking off sections (as I'm a bit weary when it comes to deleting nodes needed for blocking in UDK), it is fairly painless to remove them and will stop Unreal from arguing with you. Go ahead and delete all the nodes listed. Also, if you are getting a warning about a KillZ not being set, lets go ahead and set that.
The KillZ line is basically the distance from origin in which a plane should be created that instantly kills anything that goes below it. This prevents things from falling indefinitely, like players, enemy units, and etc. It would be pretty bad if through some AI mishap an AI falls through the world and never died. Setting a KillZ will kill it for us if it falls through the world for whatever reason so if this happens as a player is playing the game, they most likely won't notice or mind a random enemy dying.
With these errors taken care of, UDK seemed to have decided that my starting area had some unneeded nodes. Just for reference, this is the paths around my airship which UDK has cleaned.
You should notice that your player start is connected to the same path. This is because PlayerStarts are actually Path Nodes, but have been given the special ability to spawn players. If you wanted to, you could of built the pathing of your level solely with player starts. Lets start getting the enemy crowd system implemented now!
The Crowd system within Unreal is a way to have lots and lots of things running around at a very decent performance, as they are much lighter weight versions of pawns and usually only exist as cannon fodder. They also even stick together and walk around the player if needed based on a series of inputs you can give, so lets get started. The first thing we are going to need to do is create all the spawn and destination points for the crowd system. Both spawns and destinations are instances of the GameGrowdDestination actor. I'm going to go ahead and place these by copying path nodes to their position and then replacing them with GameCrowdDestination actors.
With these destination actors in place, we need to know set our spawn points to know the destination of their spawned crowds. This is rather simple, each GameCrowdDestination has an array of other GameCrowdDestinations which tell it where the things it spawns should head towards. To assign our airbase destination to these spawn points, all we have to do is select all the spawn points, bring up the properties, lock the property selection (so we can select our airbase without the property window updating), and then click the green arrow in the Next Destinations array to assign the airbase to all of the spawn points.
With these assigned, we can now open up Kismet to spawn them on level start up just to make sure our system is working. The kismet sequence itself is simple, but getting the crowd system to work fully is a little confusing if you don't know how to work with archetypes or don't know which classes are involved. The kismet sequence will need a level loaded event, a UT Crowd Spawner action, and kismet variables containing our spawn points for the UT Crowd Spawner action to know where to spawn to.
Once that is set up, there are more properties we must first set in the UT Crowd Spawner action. The properties I set were to cycle the spawn location instead of using random spawns, I've also enabled dynamic lighting for the spawned actors, and lastly we need to set up the Crowd Agent List which will take a little bit of work.
The Crowd Agent List is a list of crowd agent archetypes to spawn from, and this is where you would create different archetypes for different enemies, but for now we will just create a generic robot to shoot that comes standard with UDK. To create an archetype of a GameCrowdAgent, we must find it or a derived class from it in the actor classes, right click it, and click create archetype. Alternatively you can also place an instance of an an actor and then right click the in-game instance to create an archetype. We won't be adjusting any settings for the archetype just yet, we just need one created.
Now that we have an archetype of a GameCrowdAgent, the kismet action is looking for a list of GameCrowdAgents and not a singular GameCrowdAgent. The kismet action takes this list in the form of another archetype of the class GameCrowd_ListOfAgents. GameCrowd_ListOfAgents is not an "actor" so its a bit tricky to find in the actor classes tab, be sure to uncheck Use Actor as Parent to see it. Then create an archetype of it, open it up, and assign our first archetype as an agent archetype. Then select the list archetype and assign it to the UT Crowd Spawner Crowd Agent List. Phew.
With this done, lets go ahead and save our map, save our packages, and get ready to see what happens!
4. Just When Things Look Promising, They Become Very Very Wrong.
Much to my surprise, I loaded up the game and this is what I saw. The 5 hours stated in the video also includes the time of me taking pictures and writing how I did stuff. <_<. The actual path creation took an hour at most, but I did get stuck on researching the Scout code for awhile to figure out why my UDK kept crashing badly.
This is when I realized Crowd Agents don't have any support at all for path nodes. Initially I saw your teams map and saw the use of Pylons, but I'm a little bit prejudice against Pylons in combination with Crowd Agent destinations as sometimes they result in really odd AI behavior. Needless to say, I shouldn't of assumed that Crowd Agents also have native support for path nodes and should of researched my design implementation first. While at the moment the paths that were built are not being used at all, they still may be of importance in the future if we have a need to create AI with classes that are not crowd objects, such as perhaps a co-op bot or a type of weapon that you can manually place control points where you please and it will travel that patrol path shooting everything in sight.
With that said, I started building Crowd Agent destinations on the corners of my routes where the tiled roads change direction by copy and pasting corner path nodes and then converting them to Crowd Agent destinations.
Instead of linking the spawn destinations to the airbase destinations like before hand, we just link a series of destination points together so that the AI uses those paths to get to the airbase. Also, for the spawn destinations I enabled Line Spawner and set the Spawn Radius to 150 to try to get the AI off the sides of the walls a bit as sometimes they tend to hop up on the edge. We can adjust these settings over time as we test during development.
By decreasing the repulsion strength units will stick closer together, and by increasing the group attraction strength it will make the units want to stick together. The results from this are rather pleasing.
With the enemy units now correctly following their paths, its time to move on to setting up an isometric camera and get out of this third person mode.