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:
-
Active flag: Each bullet has an
activeattribute. When collision is detceted, setself.active = False. -
Skip inactive bullets: Both
update()anddraw()methods checkif not self.active: returnto prevent further movement or drawing. -
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. -
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.