Stage 5: Move the Aliens
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.
downward enemy movement and cleanup at the floor
how two systems can run in parallel inside one game loop
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.
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
Try this
Three short experiments. Predict before you run, then test your guess.
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?
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.
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 thatscreen.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 surescreen.listen()is before thewhile True:loop, not inside it.