[challenge]Game Development with Pygame
Project: Tower Defense
# theory
what you're building
Tower Defense: enemies walk down a path, you place towers to shoot them. More complex than Brick Breakaway because you need waypoint pathfinding, targeting logic, and projectiles that track moving targets.
🎬 gerber's video breakdown (12 videos)
Video 1: Project Setup
- Folder structure, main.py, settings.py
- Basic window and game loop
Video 2: Game Settings
- Constants file: screen size, colors
- Tower cost, enemy speed, projectile damage
- Starting gold and lives
Video 3: Game Loop Setup
- Clock and dt for frame-rate independence
- Event handling structure
- Update/draw pattern
Video 4: Adding the Map
- Loading tilemap from 2D list
- 0 = grass (buildable), 1 = path
- Drawing tiles with nested loops
Video 5: Creating the Level Class
- Encapsulate map data
- Tile drawing method
can_build_tower(x, y)check
Video 6: Adding Level Data and Cursors
- Cursor highlight for tower placement
- Click detection on tiles
- Visual feedback before building
Video 7: Enemy Class Attributes
- Waypoints list (path to follow)
- current_waypoint index
- Speed, HP, alive flag
Video 8: Enemy Methods and Spawning
move_toward_waypoint()using normalize- Spawn timer, enemy list
- Check if reached end
Video 9: Tower Class and Spawning
- Click to place (if gold and valid tile)
- Range circle for targeting
- Find nearest enemy in range
Video 10: Projectile Class
- Velocity toward target at creation
math.atan2(dy, dx)for angle- Store reference to target sprite
Video 11: Firing Projectiles
- Tower fire rate / cooldown timer
- Spawn projectile toward nearest enemy
- Track cooldown between shots
Video 12: Collision Detection
- Projectile hits enemy (distance check)
- Deal damage, kill when HP <= 0
- Remove projectile after hit
project structure
TowerDefense/
├── main.py
├── settings.py
├── level.py # map data + waypoints
├── enemy.py
├── tower.py
└── projectile.py
key concepts
Waypoint Path Following
class Enemy:
def __init__(self, waypoints):
self.waypoints = waypoints
self.wp_index = 0
self.pos = pygame.math.Vector2(waypoints[0])
self.speed = 80
def update(self, dt):
if self.wp_index >= len(self.waypoints):
return True # reached end, damage player
target = pygame.math.Vector2(self.waypoints[self.wp_index])
direction = target - self.pos
dist = direction.length()
if dist < self.speed * dt:
self.pos = target
self.wp_index += 1
else:
direction = direction.normalize()
self.pos += direction * self.speed * dt
return False
Tower Targeting with math.atan2
import math
def find_closest_enemy(tower_pos, enemies, tower_range):
closest = None
closest_dist = tower_range + 1
for enemy in enemies:
if not enemy.alive:
continue
dist = math.dist(tower_pos, enemy.pos)
if dist <= tower_range and dist < closest_dist:
closest = enemy
closest_dist = dist
return closest
def calc_projectile_velocity(start, target_pos, speed):
dx = target_pos[0] - start[0]
dy = target_pos[1] - start[1]
angle = math.atan2(dy, dx)
return math.cos(angle) * speed, math.sin(angle) * speed
Projectile Tracking
class Projectile:
def __init__(self, pos, target):
self.pos = pygame.math.Vector2(pos)
self.target = target # reference to enemy
self.speed = 400
self.damage = 25
self.alive = True
def update(self, dt):
if not self.target or not self.target.alive:
self.alive = False
return
target_pos = pygame.math.Vector2(self.target.pos)
direction = target_pos - self.pos
dist = direction.length()
if dist < self.speed * dt:
self.target.hp -= self.damage
if self.target.hp <= 0:
self.target.alive = False
self.alive = False
else:
self.pos += direction.normalize() * self.speed * dt
tips
- Gerber tip: Store enemy reference in projectile, not just position; tracks moving targets
- Tip:
math.atan2(dy, dx)gives angle in radians; essential for aiming - Tip: Check
enemy.alivebefore targeting; prevents crashes - Tip: Wave scaling: increase enemy HP/speed each wave for difficulty curve
common mistakes
- Storing position instead of reference: projectiles miss moving enemies
- Not checking if target is alive: crashes when enemy dies mid-flight
- Forgetting to normalize direction: speed varies with distance
- Spawning too many enemies at once: overwhelms player immediately
step 2 options
- Tower Sprite (5pts): load image instead of drawing shape
- Projectile Rotation (5pts): rotate sprite to face target
- Wave Balance (5pts): enemies scale with waves
- Enemy HP Bar (10pts): draw health above enemies
- Additional Levels (10pts): new maps after X waves
- New Tower Type (15pts): subclass with splash damage or slow
# examples [2]
Step 2 option (10pts); health bar above enemies
pygame needs a real window — copy this into a .py file and run it locally.
Step 2 option (15pts); damages all enemies near target
pygame needs a real window — copy this into a .py file and run it locally.
# challenges [2]