๐Ÿ My First Python Game ยท Episode 7 of 7 ยท See All Episodes
๐ŸŒฒ Episode 7 ยท Series Finale ยท Upgrades Episode 2

Level Up!
Platformer Part 2

The finale upgrades your Episode 2 platformer into a real game: patrolling enemies you can stomp, a scrolling camera, moving platforms, spikes, checkpoints and a proper multi-level structure.

๐Ÿ‘ถ Ages 10+ โฑ๏ธ ~2 Hours ๐Ÿ Python โœ“ Free
๐Ÿ‘พ Patrol enemies ๐Ÿฅพ Stomp mechanic ๐ŸŽฅ Camera scroll ๐Ÿ›— Moving platforms โš ๏ธ Hazards ๐Ÿšฉ Checkpoints
โญ
0 XP
Level 1
๐Ÿ”ฅ0
Your Progress 0 / 6 steps
1
๐Ÿ‘พ
Patrolling Enemies
Walkers that pace between two x positions
Active
๐ŸŽฏ
Goal for this step

Add enemies that patrol platforms and hurt the player on contact.

  • 1Open your Episode 2 platformer, the finale builds on it.
  • 2Enemies: list of dicts, rect, vx, and patrol bounds min_x / max_x.
  • 3Each frame: move by vx; at a bound, flip vx (bounce between posts).
  • 4Touching one costs the player a respawn (for now, stomping comes next). Draw them red.
game.py
enemies = [
    {"rect": pygame.Rect(390, 236, 30, 24), "vx": 2, "min_x": 380, "max_x": 500},
    {"rect": pygame.Rect(60, 416, 30, 24),  "vx": 2, "min_x": 40,  "max_x": 700},
]

for en in enemies:
    en["rect"].x += en["vx"]
    if en["rect"].x <= en["min_x"] or en["rect"].x >= en["max_x"]:
        en["vx"] = -en["vx"]
โœ๏ธ
Fill in the Blanks
+15 XP
Each enemy stores patrol limits min_x and . Reaching either limit flips , creating the pacing walk.
๐Ÿง 
Knowledge Check
+15 XP
The patrol pattern (move, flip at bounds) is identical to which earlier mechanic?
AThe maze ghost chase
BThe Space Invaders wave bouncing off walls
CThe fruit spawn timer
2
๐Ÿฅพ
The Stomp
Land on enemies to squash them; touch them to get hurt
Locked
๐ŸŽฏ
Goal for this step

Implement the most famous mechanic in games: the head-stomp.

  • 1On player-enemy collision, check HOW: falling (vy > 0) with your bottom near their top = STOMP.
  • 2Stomp: remove the enemy, bounce the player (vy = -10), the bounce is what makes it feel amazing.
  • 3Any other contact: hurt, respawn at start (checkpoints later).
  • 4This is exactly the platform-landing check re-aimed at a moving target. Old skill, new use.
game.py
for en in enemies[:]:
    if player.colliderect(en["rect"]):
        if vy > 0 and player.bottom - vy <= en["rect"].top + 8:
            enemies.remove(en)      # SQUASH!
            vy = -10                # bounce off the kill
            score += 5
        else:
            player.x, player.y = 100, 380   # ouch
            vy = 0
โŒจ๏ธ
Code Challenge
+20 XP
Only squash when falling onto the enemyโ€™s head:
game.py
if vy >  and player.bottom - vy <= en["rect"].top + 8:
    enemies.(en)
    vy = 
๐Ÿ’ก Hint: Falling means positive vy; the enemy leaves the list; the player rebounds upward with a negative velocity.
๐Ÿง 
Knowledge Check
+15 XP
Why bounce the player after a stomp?
ATo prevent double kills
BFeedback and flow, the bounce confirms the kill and chains into the next jump. It is why Mario feels good
CGravity requires it
3
๐ŸŽฅ
The Scrolling Camera
A wide level with a camera that follows the player
Locked
๐ŸŽฏ
Goal for this step

Grow the level to 2400 px wide and scroll the view to follow the player.

  • 1A camera in 2D is ONE number: cam_x = player.centerx - 400 (keep the player centred).
  • 2Clamp cam_x between 0 and level_width - 800.
  • 3Draw EVERYTHING offset: screen.blit / draw at (thing.x - cam_x, thing.y).
  • 4Movement/collision code does not change at all, the world is real, only the VIEW shifts. Stretch your platforms out to x = 2400 and explore.
game.py
LEVEL_W = 2400

cam_x = player.centerx - 400
cam_x = max(0, min(LEVEL_W - 800, cam_x))

# every draw call subtracts cam_x:
pygame.draw.rect(screen, (255, 119, 0),
    (player.x - cam_x, player.y, player.width, player.height))
for plat in platforms:
    pygame.draw.rect(screen, (6, 214, 160),
        (plat.x - cam_x, plat.y, plat.width, plat.height))
โœ๏ธ
Fill in the Blanks
+15 XP
The camera is just one number, , clamped between 0 and LEVEL_W - . Game logic is unchanged, only subtracts the camera.
๐Ÿง 
Knowledge Check
+15 XP
The player never actually stays "centred on screen" in the code. What really happens?
AThe player teleports each frame
BThe world is drawn shifted by cam_x, the player moves through a big world, the window is just a viewport
CThe window moves on your desktop
4
๐Ÿ›—
Moving Platforms
Platforms that carry the player
Locked
๐ŸŽฏ
Goal for this step

Add platforms that patrol, and carry the player with them.

  • 1A moving platform = the enemy patrol pattern applied to a platform: vx + bounds.
  • 2The magic line: when standing ON one, add its vx to the playerโ€™s x, otherwise it slides out from under you!
  • 3Track which platform you landed on this frame; apply its movement after collision.
  • 4Add one crossing a deadly gap. Instant classic level design.
game.py
movers = [{"rect": pygame.Rect(900, 320, 120, 16),
           "vx": 2, "min_x": 900, "max_x": 1200}]

for m in movers:
    m["rect"].x += m["vx"]
    if m["rect"].x <= m["min_x"] or m["rect"].x >= m["max_x"]:
        m["vx"] = -m["vx"]

# in the landing loop, also check movers; if landed on one:
riding = m
# after collisions:
if riding:
    player.x += riding["vx"]     # carried along!
โŒจ๏ธ
Code Challenge
+20 XP
Carry the player with the platform they stand on:
game.py
if riding:
    player.x += riding]
๐Ÿ’ก Hint: Add the platformโ€™s horizontal velocity to the player so they move together.
๐Ÿง 
Knowledge Check
+15 XP
Without the carry line, standing on a moving platform makes the playerโ€ฆ
AMove twice as fast
BSlide off as the platform moves from under them, the platform moves, they do not
CSink through it
5
โš ๏ธ
Spikes & Checkpoints
Deadly tiles and fair respawn points
Locked
๐ŸŽฏ
Goal for this step

Add hazards that kill and checkpoints that make death fair.

  • 1Spikes: a list of Rects sitting on floors/gaps; touching one = death. Draw as red triangles (polygon per spike).
  • 2Checkpoints: flag Rects; touching one sets respawn = (flag.x, flag.y - 44).
  • 3ALL deaths (spikes, enemies, falling) now respawn at respawn, not the level start.
  • 4Design rule: checkpoint BEFORE every hard section, never after. Deaths teach; long walks back just bore.
game.py
spikes = [pygame.Rect(760, 424, 40, 16), pygame.Rect(1400, 424, 80, 16)]
checkpoints = [pygame.Rect(1000, 380, 12, 60), pygame.Rect(1900, 380, 12, 60)]
respawn = (100, 380)

for cp in checkpoints:
    if player.colliderect(cp):
        respawn = (cp.x, cp.y - 44)

def die():
    global vy
    player.x, player.y = respawn
    vy = 0

for sp in spikes:
    if player.colliderect(sp):
        die()
โœ๏ธ
Fill in the Blanks
+15 XP
Touching a checkpoint updates the point. Every kind of death calls one shared () function, fix respawn logic once, it works everywhere.
๐Ÿง 
Knowledge Check
+15 XP
Best checkpoint placement isโ€ฆ
AAt the very end of the level
BRight before each difficult section, retry the challenge, not the walk to it
CRandom
6
๐Ÿฐ
Levels & The Finale
A level system, a final door, and your series wrap
Locked
๐ŸŽฏ
Goal for this step

Chain levels with a load function and finish the series like a boss.

  • 1Wrap each levelโ€™s data (platforms, enemies, movers, spikes, checkpoints, flag) in a function returning a dict, load_level(n) swaps everything.
  • 2The end-of-level door advances n; after the last, roll your victory screen with total score.
  • 3Series wrap, you have built: game loops, input, gravity, lists, grids, cameras, AI, save files, juice. Every 2D game is a remix of these.
  • 4Your real finale: pick an idea you love and build it. You have the toolkit. ๐Ÿ
game.py
def load_level(n):
    if n == 0:
        return {
            "platforms": [pygame.Rect(0, 440, 2400, 40), ...],
            "enemies":   [...],
            "movers":    [...],
            "spikes":    [...],
            "checkpoints": [...],
            "door": pygame.Rect(2320, 380, 30, 60),
        }
    if n == 1:
        return { ... }

level = load_level(0)
current = 0

if player.colliderect(level["door"]):
    current += 1
    if current < 2:
        level = load_level(current)
        die()   # reuse: places player at respawn start
    else:
        state = "CHAMPION"
โŒจ๏ธ
Code Challenge
+20 XP
Advance through levels using the loader:
game.py
current += 1
if current < :
    level = (current)
else:
    state = "CHAMPION"
๐Ÿ’ก Hint: Two levels exist; the loader function returns the next one; otherwise you are the champion.
๐Ÿง 
Knowledge Check
+15 XP
Across all 7 episodes, which single idea appeared in EVERY game you built?
AScreen shake
BThe game loop: handle input โ†’ update the world โ†’ draw, repeated 60 times a second
CBrick walls
๐ŸŽ‰๐Ÿ†๐ŸŽฎโœจ๐ŸŽ‰
Workshop Complete!
Enemies, camera, moving platforms, checkpoints, seven episodes and you are genuinely a Python game developer. Build your own idea next. ๐Ÿ๐Ÿ†
0
Total XP
1
Level
0
Best Streak
0%
Accuracy
โญ View My Progress & Certificates

This workshop was free and took many hours to build. If it helped you learn something new, consider supporting the project.

☕ Support on Ko-fi