Build Asteroids using Python and Pygame

Lesson 5 of 7

Shooting Bullets

Dodging is only half of Asteroids, next you'll give the ship a way to fight back.

A Bullet class

import math
import pygame

class Bullet:
    def __init__(self, x, y, angle):
        self.x = x
        self.y = y
        radians = math.radians(angle)
        self.dx = -math.sin(radians) * 10
        self.dy = math.cos(radians) * -10
        self.life = 60  # frames until it disappears, about 1 second at 60 FPS

    def update(self):
        self.x += self.dx
        self.y += self.dy
        self.life -= 1

    def is_dead(self):
        return self.life <= 0

    def draw(self, screen):
        pygame.draw.circle(screen, (255, 220, 100), (int(self.x), int(self.y)), 3)

A bullet reuses the same angle-to-direction math as the ship's update, fired at a fixed speed (10) in whatever direction the ship was facing at the moment it fired. The life counter, decremented every frame, is what makes bullets disappear on their own instead of flying forever, is_dead() gives the rest of the code a clean way to ask "should this be removed yet?".

Firing on spacebar, with a cooldown

bullets = []
cooldown = 0

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    keys = pygame.key.get_pressed()
    if keys[pygame.K_SPACE] and cooldown == 0:
        bullets.append(Bullet(player.x, player.y, player.angle))
        cooldown = 15  # frames before you can fire again

    if cooldown > 0:
        cooldown -= 1

    for bullet in bullets:
        bullet.update()
    bullets = [b for b in bullets if not b.is_dead()]

    # ... player and asteroid updates from earlier lessons ...

    screen.fill((10, 10, 30))
    for bullet in bullets:
        bullet.draw(screen)
    pygame.display.flip()
    clock.tick(60)

Two details are doing the real work here:

  • The cooldown counter stops holding spacebar from firing a new bullet every single frame (60 a second!), a fixed delay between shots is what makes it feel like a weapon instead of a hose.
  • The list comprehension [b for b in bullets if not b.is_dead()] rebuilds the bullets list containing only the ones still alive, this is the standard Python idiom for "remove items matching a condition" from a list, since you can't safely remove items from a list while iterating over it directly.

TIP

This same "append when spawned, filter out when dead" pattern is exactly how you'll manage destroyed asteroids in the next lesson too, it's worth getting comfortable with here first.

📝 Bullets Quiz

Passing score: 70%
  1. 1.Why does the bullet-firing code track a cooldown counter?

  2. 2.[b for b in bullets if not b.is_dead()] creates a new list containing only the bullets that are still alive.

  3. 3.A bullet's ____ counter decreases by one every frame and determines when it disappears.