Skip to main content

Stage 5: Move the Aliens

Course progressStage 5 of 10
~35 min
One game, one Trinket

Keep building in your saved Python Arcade project.

This stage is part of the same game you started in Setup. Do the work in your own remixed Trinket — it’s the workspace on the right of this page.

Build

downward enemy movement and cleanup at the floor

Learn

how two systems can run in parallel inside one game loop

Ship

aliens that drift toward the player and disappear at the bottom

The big idea

Your game already has one system moving every frame: lasers climbing up. Today we add a second system that runs alongside it: aliens drifting down. Same game loop, two passes — one for lasers, one for aliens. The loop is the conductor; each system is a player in the orchestra.

The structure of the alien movement code is going to look almost exactly like the laser movement code, but mirrored:

Stage 3 — Lasers (up): Stage 5 — Aliens (down):

for laser in lasers[:]: for alien in aliens[:]:
laser.sety(... + LASER_SPEED) alien.sety(... - ALIEN_SPEED)
if laser.ycor() > TOP: if alien.ycor() < BOTTOM:
hide + remove hide + remove

Read those two blocks side by side. Almost every line is the same shape. The differences — lasers vs aliens, + vs -, TOP vs BOTTOM — are the only places the two systems disagree on direction. Once you see this mirror, you understand how every moving thing in every video game gets updated.

The other new thing today is a frame delay. Without one, the game loop runs as fast as your computer can manage — which on Trinket means too fast. A tiny pause at the end of each frame slows everything down to a playable speed. The pause sets the framerate — how many frames the game draws per second.

Before you start

Your game should spawn aliens near the top of the screen.

Build it

Step 1 — Pick the alien speed

Speed is the dial that decides how much threat each alien carries. A fast alien punishes hesitation; a slow alien gives the player room to think. We start slow and tune later.

Add this near your alien constants:

ALIEN_SPEED = 2

Lasers used LASER_SPEED = 18. Aliens move much slower than lasers because the player needs time to react. Speed is story: fast lasers feel like a weapon, slow aliens feel like a creeping threat.

Step 2 — Walk the alien list every frame

This is the mirror of Stage 3's laser loop, but going down instead of up. We subtract ALIEN_SPEED from each alien's Y position each frame, and we clean up any alien that crosses the bottom edge.

Inside while True:, after the laser loop, add:

for alien in aliens[:]:
alien.sety(alien.ycor() - ALIEN_SPEED)

if alien.ycor() < BOTTOM:
alien.hideturtle()
aliens.remove(alien)

aliens[:] makes a copy of the list — the same trick from Stage 3, for the same reason. Walking a copy means we can safely remove aliens from the real list as we go.

Run the game. Aliens should now drift down the screen and disappear when they pass BOTTOM.

Step 3 — Slow the loop to a playable speed

Without a delay, your computer runs the game loop as fast as it can — which is way faster than human reaction time. We slip a tiny pause into every frame so the whole game runs at a steady pace.

At the top of the file, with your other imports, add:

import time

Then at the bottom of the while True: loop, just before screen.update(), add:

time.sleep(0.02)

time.sleep(0.02) means "pause this program for 0.02 seconds." With a 20-millisecond pause per frame, the loop runs roughly 50 times a second — fast enough for smooth motion, slow enough for the player to keep up.

Run the game again. Lasers should still feel snappy, aliens should drift down at a readable pace, and the cannon should respond cleanly to the keys.

Understand it

The most important idea in this stage is parallel systems sharing one loop. Right now the game updates lasers, then aliens, then redraws — every frame, in that order. As we add scoring, lives, timers, and difficulty over the next five stages, every new system slots into this same loop. The loop never gets more complicated structurally — it just gets more passes inside it.

time.sleep(0.02) deserves a second look. It might feel weird that the way to speed up motion is sometimes to slow down the loop. But think about it this way: a faster loop runs the alien movement more often, so even with ALIEN_SPEED = 2 each frame, a too-fast loop adds up to too much movement per second. The frame delay isn't there to make the game slower — it's there to make the game consistent. Without it, the same code runs at different speeds on different computers, which is a nightmare to design around.

We cleanup aliens at the floor for the same reason we cleanup lasers at the top: every Turtle costs the computer a little work, and any object that's off-screen and forgotten about is wasted work that eventually slows the game down. The pattern is let no orphan Turtle live.

Try this

Learning beat

Try this

Three short experiments. Predict before you run, then test your guess.

Predict first

Set ALIEN_SPEED = 20 and run. Decide before you click run: do you think the game becomes harder, or just impossible? Where is the line between difficulty and unfairness?

Compare

Comment out the time.sleep(0.02) line and run. Then put it back and try time.sleep(0.1). Now try time.sleep(0.005). Which felt smooth? Which felt broken? Frame timing is invisible when it works — and obvious when it doesn't.

Connect

Stage 6 detects when a laser hits an alien. To check that, you need to compare every laser to every alien and see how close they are. Look at your two loops. What's the obvious next move?

Test your stage

  • Aliens move steadily downward.
  • Aliens disappear when they pass the bottom of the screen.
  • Lasers still move upward.
  • The cannon still responds to arrow keys.
  • The game runs at a steady speed — no sudden bursts or freezes.
  • Design check. Play for a full minute. Are the aliens slow enough that you feel in control, or fast enough that a missed laser actually costs you?

If it breaks

  • Aliens move upward instead of down. Look at the line alien.sety(alien.ycor() - ALIEN_SPEED). The - is what makes aliens fall. If you wrote +, they climb. Coordinates: down is negative in Turtle.
  • The game gets slower and slower over time. That's almost always a cleanup problem. Re-read the if alien.ycor() < BOTTOM: block. Aliens that pass the bottom must be hidden and removed from the list. If only one of those happens, the game is hauling around dead aliens forever.
  • Movement is jumpy or choppy. Two suspects. First, check that screen.tracer(0) (from Stage 3) is still in the program. Second, check that screen.update() is inside the loop, not outside. The screen must redraw once per frame.
  • The game freezes when I press a key. time.sleep(0.02) is probably running while the screen is trying to listen. Make sure screen.listen() is before the while True: loop, not inside it.