- Genre: Tower Defense
- Strategic placement, resource management, and escalating difficulty define tower defense.
- Core Loop
- Prepare
-
- Build/upgrade towers with available currency
- Wave
-
- Enemies spawn and traverse path toward goal
- Defend
-
- Towers auto-target and damage enemies
- Reward
-
- Kills grant currency
- Escalate
- Waves increase in difficulty/complexity NEVER Do in Tower Defense Games NEVER make all towers equally viable — If Sniper = same DPS as Machine Gun, no strategic choice. Each tower MUST have distinct niche (AoE, slow, armor pierce, anti-air). NEVER use synchronous NavigationServer baking for mazing — NavigationRegion2D.bake_navigation_polygon() blocks main thread. Use NavigationServer2D.get_maps() + worker thread OR fixed paths. NEVER let players fully block the exit path — In mazing TDs, validate NavigationServer2D.map_get_path(start, goal) before tower placement. Empty path = illegal build. NEVER use Area2D.get_overlapping_bodies() every frame — 500 enemies × 60fps = 30k collision checks. Store bodies_entered in array, remove on body_exited . Query once. NEVER make early waves feel like busywork — First 3 waves should introduce mechanics, not bore. Start timer at 50% or give "early call" bonus to skip. NEVER allow death spirals without catch-up mechanics — 1 leaked enemy → less money → harder next wave → inevitable loss. Add interest on saved money OR discrete wave difficulty. Phase Skills Purpose 1. Grid/Path godot-tilemap-mastery , navigation-2d Defining where enemies walk and towers build 2. Towers math-geometry , area-2d Range checks, rotation, projectile prediction 3. Enemies path-following , steering-behaviors Movement along paths 4. Management state-machines , loop-management Wave spawning logic, game phases 5. UI ui-system , drag-and-drop Building towers, inspecting stats Architecture Overview 1. Wave Manager Handles the timing and godot-composition of enemy waves.
wave_manager.gd
extends Node signal wave_started ( wave_index : int ) signal wave_cleared signal enemy_spawned ( enemy : Node2D ) @ export var waves : Array [ Resource ]
Array of WaveDefinition resources
var current_wave_index : int = 0 var active_enemies : int = 0 func start_next_wave ( ) -> void : if current_wave_index
= waves . size ( ) : print ( "All waves cleared!" ) return var wave_data = waves [ current_wave_index ] wave_started . emit ( current_wave_index ) _spawn_wave ( wave_data ) current_wave_index += 1 func _spawn_wave ( wave : WaveResource ) -> void : for group in wave . groups : await get_tree ( ) . create_timer ( group . delay ) . timeout for i in group . count : var enemy = group . enemy_scene . instantiate ( ) add_child ( enemy ) active_enemies += 1 enemy . tree_exiting . connect ( _on_enemy_died ) await get_tree ( ) . create_timer ( group . interval ) . timeout func _on_enemy_died ( ) -> void : active_enemies -= 1 if active_enemies <= 0 : wave_cleared . emit ( ) 2. Tower Logic (State Machine) Towers act as autonomous agents. States : Idle , AcquireTarget , Attack , Cooldown . Targeting Priority : First , Last , Strongest , Weakest , Closest .
tower.gd
extends Node2D var targets_in_range : Array [ Node2D ] = [ ] var current_target : Node2D func _physics_process ( delta : float ) -> void : if current_target == null or not is_instance_valid ( current_target ) : _acquire_target ( ) if current_target : _rotate_turret ( current_target . global_position ) if can_fire ( ) : fire_projectile ( ) func _acquire_target ( ) -> void :
Example: Target closest to end of path
- var
- max_progress
- =
- -
- 1.0
- for
- enemy
- in
- targets_in_range
- :
- if
- enemy
- .
- progress
- >
- max_progress
- :
- current_target
- =
- enemy
- max_progress
- =
- enemy
- .
- progress
- 3. Pathfinding Variants
- A. Fixed Path (Kingdom Rush style)
- Enemies follow a pre-defined
- Path2D
- .
- Implementation
- :
- PathFollow2D
- as parent of Enemy.
- Pros
-
- Deterministic, easy to balance, optimized.
- Cons
-
- Less player agency in shaping the path.
- B. Mazing (Fieldrunners style)
- Players build towers to block/reroute enemies.
- Implementation
- :
- NavigationAgent2D
- on enemies. Towers update
- NavigationRegion2D
- (bake on separate thread).
- Pros
-
- High strategic depth.
- Cons
-
- Computationally expensive recalculation, needs anti-blocking logic (don't let player seal the exit).
- Key Mechanics Implementation
- Targeting Math (Projectile Prediction)
- To hit a moving target, you must predict where it will be.
- func
- get_predicted_position
- (
- target
- :
- Node2D
- ,
- projectile_speed
- :
- float
- )
- ->
- Vector2
- :
- var
- to_target
- =
- target
- .
- global_position
- -
- global_position
- var
- time_to_hit
- =
- to_target
- .
- length
- (
- )
- /
- projectile_speed
- return
- target
- .
- global_position
- +
- (
- target
- .
- velocity
- *
- time_to_hit
- )
- Economy
- Money management is the secondary core loop.
- Kill Rewards
-
- Direct feedback for success.
- Interest/Income
-
- Rewarding saved money (risk/reward).
- Early Calling
-
- Bonus money for starting the next wave early.
- Common Pitfalls
- Death Spirals
-
- If a player leaks one enemy, they lose money/lives, making the next wave harder, leading to inevitable failure.
- Fix
-
- Catch-up mechanics or discrete wave difficulty.
- Useless Towers
-
- Every tower type must have a distinct niche (AoE, Slow, Armor Pierce, Anti-Air).
- Path Blocking
-
- In mazing games, ensure players cannot completely block the path to the exit. Use
- NavigationServer2D.map_get_path
- to validate placement before building.
- Godot-Specific Tips
- Physics Layers
-
- Put enemies on a specific layer (e.g., Layer 2) and tower "range" Areas on a different mask to avoid towers detecting each other or walls.
- Area2D Performance
-
- For massive numbers of enemies, avoid
- monitorable/monitoring
- on every frame if possible. Use
- PhysicsServer2D
- queries for optimization if enemy count > 500.
- Object Pooling
- Essential for projectiles and enemies to avoid garbage collection stutters during intense waves. Reference Master Skill: godot-master