Handling Bullet Collision Removal in Pygame

When working with bullet collision detection in Pygame, bullets may continue moving after hitting walls if they are not properly removed from the active bullet list. The key is to remove the bullet from the list immediately upon collision detection.

Collision Detection with Bullet Removal

In the bullet's draw() or update() method, perform collision detection against wall rectangles. When a collision occurs, remove the bullet from the bullets list using remove(self).

import pygame
import sys
import math

pygame.init()

screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Bullet Collision Demo")

class Bullet:
    def __init__(self, x, y, angle):
        self.x = x
        self.y = y
        self.angle = angle
        self.speed = 5
        self.active = True

    def update(self):
        if not self.active:
            return
        rad = math.radians(self.angle)
        self.x += math.cos(rad) * self.speed
        self.y -= math.sin(rad) * self.speed

    def check_collision(self, wall_list):
        if not self.active:
            return None
        bullet_rect = pygame.Rect(int(self.x), int(self.y), 6, 6)
        for wall in wall_list:
            if bullet_rect.colliderect(wall):
                return (self.x, self.y)
        return None

    def draw(self, wall_list):
        if not self.active:
            return
        pygame.draw.rect(screen, (255, 0, 0), (int(self.x), int(self.y), 6, 6))
        collision_point = self.check_collision(wall_list)
        if collision_point:
            self.active = False
            return collision_point
        return None

# Map layout as 2D grid
map_layout = [
    [1, 1, 1, 1, 1, 1, 1, 1],
    [1, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 1, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 1],
    [1, 1, 1, 1, 1, 1, 1, 1]
]

wall_rects = []
bullet_list = []
player_x, player_y = 400, 300
player_angle = 0
clock = pygame.time.Clock()
ray_hit_point = None

# Initialize wall rectangles from map
for row_idx, row in enumerate(map_layout):
    for col_idx, cell in enumerate(row):
        if cell == 1:
            wall_rect = pygame.draw.rect(screen, (140, 240, 40), (100 + col_idx * 60, 100 + row_idx * 60, 60, 60))
            wall_rects.append(wall_rect)

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        player_angle = (player_angle + 1) % 360
    elif keys[pygame.K_RIGHT]:
        player_angle = (player_angle - 1) % 360
    elif keys[pygame.K_SPACE]:
        bullet_list.append(Bullet(player_x, player_y, player_angle))

    screen.fill((255, 255, 255))
    clock.tick(170)

    # Draw walls
    for wall in wall_rects:
        pygame.draw.rect(screen, (140, 240, 40), wall)

    # Update and draw bullets, collect hit points
    hit_points = []
    for bullet in bullet_list:
        bullet.update()
        hit = bullet.draw(wall_rects)
        if hit:
            hit_points.append(hit)

    # Remove inactive bullets
    bullet_list = [b for b in bullet_list if b.active]

    # Draw ray from player to hit point
    if hit_points:
        ray_hit_point = hit_points[-1]
        pygame.draw.line(screen, (40, 140, 40), (player_x, player_y), ray_hit_point)
        pygame.draw.circle(screen, (0, 0, 255), ray_hit_point, 5)

    pygame.display.flip()

The critical fixes in this implementation:

  1. Active flag: Each bullet has an active attribute. When collision is detceted, set self.active = False.

  2. Skip inactive bullets: Both update() and draw() methods check if not self.active: return to prevent further movement or drawing.

  3. List cleanup: After processing all bullets, filter out inactive ones with bullet_list = [b for b in bullet_list if b.active]. This ensures inactive bullets won't be drawn or updated in subsequent frames.

  4. Collision check timing: The collision detection happens after position update but before drawing, allowing immediate removal upon collision.

If using collidelistall() instead of per-rectangle collision checking, the approach remains similar. When collidelistall() returns a non-empty list, mark the bullet as inactive and store the collision point for ray rendering purposes.

The fundamental principle: collision must immediately disable the bullet's movement capability, either by marking it inactive or removing it from the update/draw loop entirely.

Tags: pygame collision-detection game-development python bullets

Posted on Sat, 18 Jul 2026 16:09:54 +0000 by almightyegg