Quick answer: pygame.sprite.collide_circle uses sprite.radius if it exists, otherwise it guesses from the rect. Set self.radius explicitly for predictable circular hits.
Bullet-vs-asteroid collision with collide_circle feels off — hits register too early or too late. Neither sprite has a radius attribute, so Pygame is estimating.
How collide_circle Picks a Radius
If a sprite has a radius attribute, collide_circle uses it. If not, it derives a radius from the rect — roughly half the diagonal — which is usually bigger than the visible shape.
Set radius Explicitly
class Asteroid(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = ...
self.rect = self.image.get_rect()
self.radius = 28 # tuned to the visible rock
Now collide_circle uses 28 — consistent and tuned to the art, not the bounding box.
collide_circle_ratio
For a quick global adjustment without per-sprite radii, collide_circle_ratio(0.75) scales the rect-derived radius by a factor. Good for prototyping; explicit radii are better for shipping.
Keep radius in Sync with Scale
If you scale a sprite at runtime, update radius too — it doesn’t auto-track the image size.
Verifying
Fire bullets at asteroids. Hits register when the circles visually overlap — not a rect-corner early, not a pixel late. Scaled asteroids collide correctly.
“collide_circle wants a radius. Set it explicitly and tune it to the art.”
Draw the collision circle in a debug overlay while tuning — the right radius is obvious in two seconds visually.