Skip to main content

Stage 3: Fire Lasers

Course progressStage 3 of 10
~40 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

a list of lasers, a fire button, and a game loop

Learn

how a game updates many moving objects every frame

Ship

lasers that launch upward from the cannon and disappear at the top

The big idea

Up until now, every object in the game has been created once and lived forever — one screen, one cannon. Lasers are different. They are temporary: a laser is born when the player presses space, climbs up the screen, and disappears when it leaves the playfield. The game might have zero lasers, or one, or three, all at the same time.

To handle "an unknown number of similar things," we use a list. A list in Python is a container that holds many items in order. You can add to it, walk through it, and remove items as the game changes.

lasers = [ laser0 , laser1 , laser2 ]
│ │ │
▼ ▼ ▼
(each one is its own Turtle, climbing the screen)

We also need to introduce the game loop. A game loop is a piece of code that runs over and over — many times per second — and on each pass it updates every moving thing in the game. One pass of the loop is called a frame. Lasers move because every frame, the loop walks the list and nudges each one upward.

The list pattern from this stage is the most reused pattern in the whole course — aliens, hits, and game-over checks all use it. Today is the day to understand it.

Before you start

Your cannon should move left and right with the arrow keys.

Build it

Step 1 — Set up the laser list and limits

A list starts empty. We also pick a top speed and a cap on how many lasers can be on screen at once. Caps keep the game readable — a screen full of lasers is a screen the player can't see through.

Add these near your other constants:

LASER_SPEED = 18
MAX_LASERS = 3
lasers = []

lasers = [] creates an empty list. By the end of the stage it will fill up and drain as the player shoots.

Step 2 — Write the fire function

Pressing space should create a new laser at the cannon's current position — unless there are already too many. We check the cap first, then build a fresh Turtle and add it to the list.

Add this function above the keyboard setup:

def fire_laser():
if len(lasers) >= MAX_LASERS:
return

laser = turtle.Turtle()
laser.penup()
laser.color("red")
laser.shape("triangle")
laser.setheading(90)
laser.turtlesize(0.5, 1)
laser.setposition(cannon.xcor(), FLOOR_LEVEL + 30)
lasers.append(laser)

len(lasers) is how many items the list currently holds. lasers.append(laser) adds the new laser to the end of the list. From this moment on, the game owns that laser through the list.

Step 3 — Bind the space key

Same pattern as Stage 2 — the screen listens, and when it sees the space bar it calls fire_laser.

Add this with your key bindings:

screen.onkeypress(fire_laser, "space")

Run the game and press space. You'll see red triangles appear at the cannon — but they will not move yet. That's the next step.

Step 4 — Build the game loop

Now we make the lasers move. We add a while True: loop at the bottom of the program that walks the laser list every frame, nudges each one upward, and removes any that have left the screen.

First, add this before your key bindings so animation is smooth:

screen.tracer(0)

screen.tracer(0) tells Turtle to stop drawing automatically. We will tell it when to draw, once per frame, with screen.update().

Now replace your final turtle.done() with this loop:

while True:
for laser in lasers[:]:
laser.sety(laser.ycor() + LASER_SPEED)

if laser.ycor() > TOP:
laser.hideturtle()
lasers.remove(laser)

screen.update()

Run the game. Press space — lasers should now climb the screen, hit the top, and disappear.

Understand it

The piece worth slowing down on is lasers[:]. That little [:] is not decoration — it makes a copy of the list. When we walk through a list with for laser in ... and remove items from the original list at the same time, the loop loses track of where it is and skips items. Walking a copy means we can safely modify the real list mid-loop. This is a real Python gotcha, and you'll see it again in Stage 5.

The screen.tracer(0) / screen.update() pair is a speed choice, not a correctness one. By default, Turtle redraws the screen after every single change — which is slow when you have lots of moving things. Turning off auto-draw and updating manually once per frame is how games keep their framerate up.

We deliberately picked MAX_LASERS = 3 instead of allowing unlimited shots. A cap makes the game better: it forces the player to aim instead of mash space, and it keeps the screen readable. Game designers call this a constraint that creates skill — the same idea that makes a chess board only 8×8.

Try this

Learning beat

Try this

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

Predict first

Change LASER_SPEED to 2. Run the game and fire a few lasers. Does the game still feel like an arcade shooter? Write one sentence about why the speed matters this much.

Compare

Comment out screen.tracer(0) (put a # in front of it) and run. Watch what happens to the cannon and the lasers as the screen tries to draw every change. Put it back when you're done.

Connect

Stage 4 spawns aliens. Aliens are also temporary game objects that need to live in a list and move every frame. Look at your laser code. What three pieces will the alien code reuse almost word-for-word?

Test your stage

  • Pressing space creates a laser at the cannon.
  • Lasers move upward smoothly.
  • Lasers disappear when they reach the top.
  • You cannot fill the screen with more than three lasers at once.
  • The cannon still moves while lasers are flying.
  • Design check. Hold the space bar for two seconds. Does the rate of fire feel right, or does the cap make the game feel slow?

If it breaks

  • The game freezes and the window stops responding. screen.update() is probably missing from inside the while True: loop, or it's outside the loop. The screen needs to be updated every frame, not once.
  • Lasers appear but don't move. Check that LASER_SPEED is a number, not zero, and that the line laser.sety(laser.ycor() + LASER_SPEED) is inside the for laser in lasers[:]: loop.
  • Lasers fly forever and the game gets slower. The if laser.ycor() > TOP: block is what cleans them up. If a laser passes the top and never gets removed, it stays in the list and the loop keeps moving it. Double-check the > direction.
  • Lasers come from the wrong place. cannon.xcor() is what aims them — it asks the cannon "where are you right now?" If lasers always start in the middle, you may have used a number like 0 instead of cannon.xcor().
  • I press space and nothing happens. Re-read your key bindings. screen.onkeypress(fire_laser, "space") must come before screen.listen().