Game Dev Lab — Day 2
Enemies & Collision
Space Shooter with Enemies
# INSTRUCTOR — Game Dev Lab Day 2: Enemies & Collision
# -------------------------------------------------------
# TEACH: Build on Day 1. Add enemies spawning at the top, collision with
# pygame.Rect, score, and lives. This is the first fully playable game.
#
# KEY CONCEPT — pygame.Rect:
# Every object gets a Rect for position + size.
# Rect.colliderect(other_rect) returns True if they overlap. That's collision.
# rect = pygame.Rect(x, y, width, height) — x,y is the TOP-LEFT corner
#
# MILESTONE (show me when done):
# Enemies fall from the top in waves.
# Bullets destroy enemies (both disappear) — score goes up.
# Enemy reaches bottom → lose a life. Game over at 0 lives.
# Score displayed on screen.
#
# EXTENSION challenges:
# - Speed enemies up as score increases
# - Add a power-up item that clears all enemies on screen
# - Show a "You Win!" screen after reaching score 20
import pygame
import sys
import random
pygame.init()
WIDTH, HEIGHT = 600, 700
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Space Shooter — Day 2")
clock = pygame.time.Clock()
font = pygame.font.SysFont("courier", 22, bold=True)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
CYAN = (0, 220, 220)
YELLOW = (255, 255, 0)
RED = (220, 50, 50)
# ── State ────────────────────────────────────────────────────────────────────
ship_rect = pygame.Rect(WIDTH // 2 - 20, HEIGHT - 70, 40, 30)
SHIP_SPEED = 5
bullets = [] # list of pygame.Rect
enemies = [] # list of pygame.Rect
score = 0
lives = 3
game_over = False
BULLET_SPEED = 9
ENEMY_SPEED = 2 # TEACH: try increasing this as score goes up
# ── Spawn helpers ─────────────────────────────────────────────────────────────
def spawn_enemy():
x = random.randint(20, WIDTH - 60)
enemies.append(pygame.Rect(x, -40, 40, 40))
# Start with a small wave
for _ in range(5):
spawn_enemy()
def fire_bullet():
cx = ship_rect.centerx
bullets.append(pygame.Rect(cx - 4, ship_rect.top - 18, 8, 18))
# ── Drawing ──────────────────────────────────────────────────────────────────
def draw_ship():
cx = ship_rect.centerx
top = (cx, ship_rect.top)
left = (ship_rect.left, ship_rect.bottom)
right = (ship_rect.right, ship_rect.bottom)
pygame.draw.polygon(screen, CYAN, [top, left, right])
def draw_hud():
# TEACH: font.render returns a Surface; blit pastes it onto the screen
hud = font.render(f"Score: {score} Lives: {lives}", True, WHITE)
screen.blit(hud, (10, 10))
def draw_game_over():
msg = font.render("GAME OVER — press R to restart", True, RED)
rect = msg.get_rect(center=(WIDTH // 2, HEIGHT // 2))
screen.blit(msg, rect)
def reset():
global score, lives, game_over
ship_rect.center = (WIDTH // 2, HEIGHT - 55)
bullets.clear()
enemies.clear()
for _ in range(5):
spawn_enemy()
score = 0
lives = 3
game_over = False
# ── Game loop ─────────────────────────────────────────────────────────────────
SPAWN_EVENT = pygame.USEREVENT + 1
pygame.time.set_timer(SPAWN_EVENT, 1500) # TEACH: custom event fires every 1.5 s
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit(); sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and not game_over:
fire_bullet()
if event.key == pygame.K_r and game_over:
reset()
if event.type == SPAWN_EVENT and not game_over:
spawn_enemy()
if not game_over:
# Move ship
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and ship_rect.left > 0:
ship_rect.x -= SHIP_SPEED
if keys[pygame.K_RIGHT] and ship_rect.right < WIDTH:
ship_rect.x += SHIP_SPEED
# Move bullets
for b in bullets[:]:
b.y -= BULLET_SPEED
if b.bottom < 0:
bullets.remove(b)
# Move enemies
for e in enemies[:]:
e.y += ENEMY_SPEED
if e.top > HEIGHT:
enemies.remove(e)
lives -= 1
if lives <= 0:
game_over = True
# Collision — bullet hits enemy
# TEACH: iterate copies because we remove during the loop
for b in bullets[:]:
for e in enemies[:]:
if b.colliderect(e):
bullets.remove(b)
enemies.remove(e)
score += 1
break # this bullet is gone — stop checking other enemies
# Draw
screen.fill(BLACK)
draw_ship()
for b in bullets:
pygame.draw.ellipse(screen, YELLOW, b)
for e in enemies:
pygame.draw.rect(screen, RED, e, border_radius=4)
draw_hud()
if game_over:
draw_game_over()
pygame.display.flip()
clock.tick(60)