Lesson 6: Add Bullets (Pygame)

In this lesson you will code bullets in Python with Pygame. When the player presses Space, a bullet appears at the tip of the ship and travels upward. No enemies yet — just firing and cleanup.

What you’ll learn: variables, lists, key events, timing (cooldown), updating position, drawing rectangles.

Step 0 — Start from Lesson 5

Open the file you built in Lesson 5 (window + left/right movement). Keep your while running: game loop.

Step 1 — Add bullet state (top of your file)

Put these just under your player variables so they’re easy to find:

# === BULLETS ===
bullets = []                 # list of bullet dictionaries
BULLET_W, BULLET_H = 4, 10   # size in pixels
BULLET_SPEED = -6            # negative = up
FIRE_COOLDOWN_MS = 250       # about 4 shots per second
last_shot_at = 0             # time of last shot (ms)
Why a list? We may have many bullets on the screen at once. A Python list lets us store all of them and update each one every frame.

Step 2 — Write a helper to spawn a bullet

Add this function above your game loop. It uses the player’s position to place the bullet at the ship’s nose and enforces a cooldown so holding Space isn’t a laser.

import pygame  # (already at the top of your file)

# ... keep your other setup code ...

def fire_bullet(now_ms, player_x, player_y, player_w):
    """Create one bullet if cooldown passed."""
    global last_shot_at, bullets
    if now_ms - last_shot_at < FIRE_COOLDOWN_MS:
        return  # too soon, wait a bit
    # center bullet at the top of the ship
    bx = player_x + player_w // 2 - BULLET_W // 2
    by = player_y - BULLET_H
    bullets.append({"x": bx, "y": by, "vy": BULLET_SPEED})
    last_shot_at = now_ms

Step 3 — Update bullets every frame

Add these functions to keep your code tidy:

def update_bullets():
    """Move bullets and remove the ones that go off-screen."""
    global bullets
    for b in bullets:
        b["y"] += b["vy"]
    # keep bullets whose bottom is still visible
    bullets = [b for b in bullets if b["y"] + BULLET_H > 0]

def draw_bullets(screen):
    BULLET_COLOR = (255, 211, 105)  # soft yellow
    for b in bullets:
        pygame.draw.rect(screen, BULLET_COLOR, pygame.Rect(b["x"], b["y"], BULLET_W, BULLET_H))

Step 4 — Hook bullets into your game loop

  1. When the player presses Space, call fire_bullet(...).
  2. Each frame, call update_bullets() before drawing.
  3. When drawing, call draw_bullets(screen) after you draw the background and player.

Inside your event loop (where you already handle ←/→):

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:
            now = pygame.time.get_ticks()
            fire_bullet(now, player_x, player_y, player_w)
    if event.type == pygame.KEYUP:
        if event.key in (pygame.K_LEFT, pygame.K_RIGHT):
            player_speed = 0

Each frame (in your main loop, after moving the player):

update_bullets()

When drawing (after filling the background and drawing the player):

draw_bullets(screen)

Step 5 — Simple player shape (if you don’t have an image)

If you didn’t load player.png in Lesson 4, you can draw a simple rectangle ship and set these sizes so bullets spawn from the center:

# player size & position
player_w, player_h = 40, 20
player_x = 800 // 2 - player_w // 2
player_y = 600 - 60

# draw the player (rectangle + tiny cockpit)
PLAYER_COLOR = (154, 180, 255)
pygame.draw.rect(screen, PLAYER_COLOR, pygame.Rect(player_x, player_y, player_w, player_h))
If you do have a sprite: use player_w = player_img.get_width() and draw with screen.blit(player_img, (player_x, player_y)).

Full example after Lesson 6

Here is a small, complete script you can run to test bullets. It uses a rectangle for the ship (no image needed).

import sys, pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Space Invader — Lesson 6")
clock = pygame.time.Clock()

# colors
SPACE = (10, 15, 30)
PLAYER_COLOR = (154, 180, 255)
BULLET_COLOR = (255, 211, 105)

# player
player_w, player_h = 40, 20
player_x = 800 // 2 - player_w // 2
player_y = 600 - 60
player_speed = 0  # -4, 0, or +4

# bullets
bullets = []
BULLET_W, BULLET_H = 4, 10
BULLET_SPEED = -6
FIRE_COOLDOWN_MS = 250
last_shot_at = 0

def fire_bullet(now_ms, player_x, player_y, player_w):
    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})
    last_shot_at = now_ms

def update_bullets():
    global bullets
    for b in bullets:
        b["y"] += b["vy"]
    bullets = [b for b in bullets if b["y"] + BULLET_H > 0]

def draw_bullets():
    for b in bullets:
        pygame.draw.rect(screen, BULLET_COLOR, pygame.Rect(b["x"], b["y"], BULLET_W, BULLET_H))

running = True
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, player_x, player_y, player_w)
        if event.type == pygame.KEYUP:
            if event.key in (pygame.K_LEFT, pygame.K_RIGHT):
                player_speed = 0

    # move player + clamp to screen
    player_x += player_speed
    pad = 10
    if player_x < pad:
        player_x = pad
    if player_x + player_w > 800 - pad:
        player_x = 800 - pad - player_w

    # update bullets
    update_bullets()

    # draw
    screen.fill(SPACE)
    pygame.draw.rect(screen, PLAYER_COLOR, pygame.Rect(player_x, player_y, player_w, player_h))
    # tiny cockpit
    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()

    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

Mini‑challenges