Defence of the tower in the mountains
A downloadable game
For this project the use of procedural generation is implemented in order to make the game more unpredictable and difficult while still maintaining balance. Made this game in my final year with help from my group partner, i worked on the level design, terrain and enemies while they worked on the player placement, attacks and user interface. We also used the URP to add an extra touch to the game and made custom shaders to have the tower change colour when health is low and for it to have changing colours for level power ups.
Some of the design patterns i used for the enemies were :
1. Flyweight Pattern
I used the Flyweight pattern to optimize memory usage for my enemy types. Instead of storing health, damage, speed, and range separately for every enemy instance, I defined them once in a static Dictionary<string, EnemyAttributes> called EnemyTypes. Each enemy just references its type ("Base", "QuickWatcher", "Tank"), and the shared attributes are fetched from the dictionary. This way, I can spawn hundreds of enemies without duplicating stats for each one.
2. Strategy Pattern
I applied the Strategy pattern to make enemy behaviors flexible. Each enemy type has its own stats (like fast but weak 'QuickWatcher' or slow but tanky 'Tank'), but they all share the same movement and attack logic. If I want to add a new enemy type, I just define its attributes in the EnemyTypes dictionary—no need to rewrite the AI. This makes it easy to balance the game or add special enemies later.
3. State Pattern
(Movement & Combat) The enemy AI switches between states implicitly: moving along waypoints (MoveAlongPath) or attacking the tower (Update). In MoveAlongPath, I check for obstacles and waypoint progress, while Update handles attack cooldowns. If I expand this, I could formalize states (e.g., 'Chasing', 'Attacking', 'Fleeing') for clearer logic.
4. Singleton Pattern
(GridManager) I needed a single, global point of control for wave progression, so I made GridManager a Singleton (GridManager.Instance). This lets enemies safely decrement enemiesLeftInWave when destroyed, and other systems (like UI) can access wave data without messy references.
5. Object Pool Pattern
(Implied for Enemies) Though not fully shown here, I plan to use an Object Pool for enemies. Instead of constantly instantiating/destroying them, I’ll reuse inactive enemies from a pool. The commented Destroy(gameObject) hints at this—it’ll be replaced by recycling enemies to boost performance during waves.
Why These Patterns?
Tower defense games need to handle many enemies efficiently while keeping code clean. Flyweight saves memory, Strategy lets me tweak enemies easily, State keeps AI manageable, Singleton coordinates global rules, and Object Pooling prevents lag spikes. Together, they make the system scalable and modular.
When it came to generating the terrain i also went a bit beyond what was necessary for the project by making a procedural generation of a land mass because i originally wanted the enemies, tower and player to spawn there but was unable to get them to spawn on the land mass and had to make a grid based system hovering above that instead. The patterns i used for the landmass are as follows:
1. Factory Method Pattern (Mesh & Texture Generation)
I used the Factory Method pattern in my procedural terrain system to create different types of map representations (noise maps, color maps, meshes) without exposing the complex generation logic. The MeshGenerator.GenerateTerrainMesh and TextureGenerator.TextureFromHeightMap methods act like factories—they take raw data (heightmaps) and return fully constructed Unity meshes or textures. This lets me switch between draw modes (like NoiseMap, ColourMap, or Mesh) in the editor seamlessly, just by calling the appropriate factory method.
2. Builder Pattern (Terrain Chunk Assembly)
For terrain chunks, I applied a Builder-like approach. The MeshData class (not fully shown here) incrementally constructs vertices, triangles, and UVs in multiple passes—first mapping border indices, then calculating vertex positions, and finally stitching triangles together. This step-by-step process lets me handle LOD (Level of Detail) simplification cleanly by adjusting the meshSimplificationIncrement parameter during the build phase.
3. Object Pool Pattern (Terrain Chunk Management)
To optimize performance, I implemented an Object Pool for terrain chunks. The UpdateVisibleChunks method recycles chunks around the player’s position: it hides distant chunks (SetVisible(false)) and either reuses existing ones from the terrainChunksDictionary or instantiates new ones. This avoids costly GameObject instantiation/destruction during runtime, which is critical for large procedural worlds.
4. Strategy Pattern (Draw Mode Selection)
The DrawMapInEditor method uses a Strategy-like pattern to switch rendering behaviors at runtime. Based on the drawMode (NoiseMap, ColourMap, Mesh, etc.), it delegates work to different algorithms—like TextureFromHeightMap for noise or GenerateTerrainMesh for 3D terrain. This makes it trivial to add new draw modes later without rewriting existing code.
5. Observer Pattern (Terrain Updates)
Though not fully shown here, the chunk system hints at an Observer-like setup. When the player moves (viewerPosition changes), UpdateVisibleChunks notifies affected chunks to update themselves (UpdateTerrainChunk). In a full implementation, I’d use events to decouple the player’s movement from chunk updates further.
6. Flyweight Pattern (Shared Terrain Data)
I reused the Flyweight pattern here too—the heightMap and colourMap data are shared across all terrain chunks. Instead of storing duplicate noise/color data per chunk, chunks reference centralized maps generated once (e.g., in GenerateMapData). This saves memory when rendering large worlds.
Why These Patterns?
Procedural terrain needs to balance flexibility and performance. Factory Methods simplify mesh/texture creation, the Builder organizes complex mesh construction, Object Pooling boosts fps, and Strategy makes rendering modes plug-and-play. Combined, they let my tower defense game generate vast, dynamic battlefields efficiently—whether for editor previews or runtime exploration.
Published | 22 days ago |
Status | Released |
Author | Melu Gula-Ndebele |
Genre | Strategy |
Leave a comment
Log in with itch.io to leave a comment.