Lesson 7: Add Enemies
Build on Lesson 6 (player + bullets). In this lesson you’ll:
- Spawn a grid of enemies
- Move them side-to-side and drop down at edges
- Detect bullet–enemy collisions and keep score
- Show You win! when all enemies are gone
Controls: Left/Right to move, Space to shoot.
Step 1 — Enemy formation (add near top)
# === ENEMIES ===
enemies = []
ENEMY_COLS, ENEMY_ROWS = 8, 3
ENEMY_SPACING_X, ENEMY_SPACING_Y = 70, 50
ENEMY_START_X, ENEMY_START_Y = 80, 60
ENEMY_W, ENEMY_H = 36, 20
enemy_dx = 1.2 # horizontal speed (pixels/frame)
ENEMY_DROP_Y = 18
def create_enemies():
global enemies
enemies = []
for r in range(ENEMY_ROWS):
for c in range(ENEMY_COLS):
enemies.append({
"x": ENEMY_START_X + c * ENEMY_SPACING_X,
"y": ENEMY_START_Y + r * ENEMY_SPACING_Y,
"w": ENEMY_W, "h": ENEMY_H,
"alive": True,
"points": 30 if r == 0 else (20 if r == 1 else 10)
})
Step 2 — Group movement
def update_enemies(screen_w):
global enemy_dx
hit_left, hit_right = False, False
for e in enemies:
if not e["alive"]: continue
nx = e["x"] + enemy_dx
if nx < 10: hit_left = True
if nx + e["w"] > screen_w - 10: hit_right = True
if hit_left or hit_right:
for e in enemies:
if e["alive"]:
e["y"] += ENEMY_DROP_Y
enemy_dx = -enemy_dx
for e in enemies:
if e["alive"]:
e["x"] += enemy_dx
Step 3 — Draw enemies
def draw_enemies(screen):
import pygame
ENEMY_COLOR = (123, 211, 255)
EYE = (11, 18, 38)
for e in enemies:
if not e["alive"]: continue
pygame.draw.rect(screen, ENEMY_COLOR, (e["x"], e["y"], e["w"], e["h"]))
pygame.draw.rect(screen, EYE, (e["x"]+6, e["y"]+6, 6, 6))
pygame.draw.rect(screen, EYE, (e["x"]+e["w"]-12, e["y"]+6, 6, 6))
Step 4 — Bullet ↔ enemy collisions + score + win
score = 0
def rects_overlap(a, b):
return (a["x"] < b["x"] + b["w"] and a["x"] + a["w"] > b["x"] and
a["y"] < b["y"] + b["h"] and a["y"] + a["h"] > b["y"])
def handle_bullet_enemy_collisions(BULLET_W, BULLET_H, bullets):
global score
for e in enemies:
if not e["alive"]: continue
for b in bullets:
if not b.get("active", True): continue
bullet_rect = {"x": b["x"], "y": b["y"], "w": BULLET_W, "h": BULLET_H}
if rects_overlap(bullet_rect, e):
e["alive"] = False
b["active"] = False
score += e.get("points", 10)
break
# keep only active bullets
return [b for b in bullets if b.get("active", True)]
Win check:
def all_enemies_defeated():
return not any(e["alive"] for e in enemies)
Full example (save as lesson7.py)
import sys, pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Space Invader — Lesson 7")
clock = pygame.time.Clock()
SPACE = (10, 15, 30)
PLAYER_COLOR = (154, 180, 255)
BULLET_COLOR = (255, 211, 105)
ENEMY_COLOR = (123, 211, 255)
EYE = (11, 18, 38)
# Player
player_w, player_h = 40, 20
player_x = 800//2 - player_w//2
player_y = 600 - 60
player_speed = 0
# Bullets
bullets = []
BULLET_W, BULLET_H = 4, 10
BULLET_SPEED = -6
FIRE_COOLDOWN_MS = 250
last_shot_at = 0
def fire_bullet(now_ms):
global last_shot_at, bullets
if now_ms - last_shot_at < FIRE_COOLDOWN_MS: return
bx = player_x + player_w//2 - BULLET_W//2
by = player_y - BULLET_H
bullets.append({"x": bx, "y": by, "vy": BULLET_SPEED, "active": True})
last_shot_at = now_ms
def update_bullets():
global bullets
for b in bullets:
if not b["active"]: continue
b["y"] += b["vy"]
if b["y"] + BULLET_H < 0: b["active"] = False
bullets = [b for b in bullets if b["active"]]
def draw_bullets():
for b in bullets:
pygame.draw.rect(screen, BULLET_COLOR, (b["x"], b["y"], BULLET_W, BULLET_H))
# Enemies
enemies = []
ENEMY_COLS, ENEMY_ROWS = 8, 3
ENEMY_SPACING_X, ENEMY_SPACING_Y = 70, 50
ENEMY_START_X, ENEMY_START_Y = 80, 60
ENEMY_W, ENEMY_H = 36, 20
enemy_dx = 1.2
ENEMY_DROP_Y = 18
def create_enemies():
global enemies
enemies = []
for r in range(ENEMY_ROWS):
for c in range(ENEMY_COLS):
enemies.append({
"x": ENEMY_START_X + c*ENEMY_SPACING_X,
"y": ENEMY_START_Y + r*ENEMY_SPACING_Y,
"w": ENEMY_W, "h": ENEMY_H,
"alive": True,
"points": 30 if r==0 else (20 if r==1 else 10)
})
def update_enemies():
global enemy_dx
hit_left, hit_right = False, False
for e in enemies:
if not e["alive"]: continue
nx = e["x"] + enemy_dx
if nx < 10: hit_left = True
if nx + e["w"] > 800 - 10: hit_right = True
if hit_left or hit_right:
for e in enemies:
if e["alive"]: e["y"] += ENEMY_DROP_Y
enemy_dx = -enemy_dx
for e in enemies:
if e["alive"]: e["x"] += enemy_dx
def draw_enemies():
for e in enemies:
if not e["alive"]: continue
pygame.draw.rect(screen, ENEMY_COLOR, (e["x"], e["y"], e["w"], e["h"]))
pygame.draw.rect(screen, EYE, (e["x"]+6, e["y"]+6, 6, 6))
pygame.draw.rect(screen, EYE, (e["x"]+e["w"]-12, e["y"]+6, 6, 6))
score = 0
def rects_overlap(a, b):
return (a["x"] < b["x"] + b["w"] and a["x"] + a["w"] > b["x"] and
a["y"] < b["y"] + b["h"] and a["y"] + a["h"] > b["y"])
def handle_bullet_enemy_collisions():
global bullets, score
for e in enemies:
if not e["alive"]: continue
for b in bullets:
if not b["active"]: continue
bullet_rect = {"x": b["x"], "y": b["y"], "w": BULLET_W, "h": BULLET_H}
if rects_overlap(bullet_rect, e):
e["alive"] = False
b["active"] = False
score += e.get("points", 10)
break
bullets = [b for b in bullets if b["active"]]
def draw_hud(surface):
font = pygame.font.SysFont(None, 28)
surface.blit(font.render(f"Score: {score}", True, (209,231,255)), (10,10))
running = True
create_enemies()
while running:
now = pygame.time.get_ticks()
for event in pygame.event.get():
if event.type == pygame.QUIT: running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT: player_speed = -4
if event.key == pygame.K_RIGHT: player_speed = 4
if event.key == pygame.K_SPACE: fire_bullet(now)
if event.type == pygame.KEYUP:
if event.key in (pygame.K_LEFT, pygame.K_RIGHT): player_speed = 0
# move player + clamp
pad = 10
player_x = max(pad, min(player_x + player_speed, 800 - pad - player_w))
update_bullets()
update_enemies()
handle_bullet_enemy_collisions()
screen.fill(SPACE)
pygame.draw.rect(screen, PLAYER_COLOR, (player_x, player_y, player_w, player_h))
pygame.draw.polygon(screen, (207,225,255), [
(player_x + player_w//2, player_y - 6),
(player_x + player_w//2 - 6, player_y),
(player_x + player_w//2 + 6, player_y)
])
draw_bullets()
draw_enemies()
draw_hud(screen)
if not any(e["alive"] for e in enemies):
font = pygame.font.SysFont(None, 40)
screen.blit(font.render("You win!", True, (134,239,172)), (340, 280))
pygame.display.flip()
clock.tick(60)
pygame.quit()
sys.exit()