Quick answer: Mask positioning uses sprite.rect.topleft. Move & clamp the rect before calling pygame.sprite.collide_mask.
A platformer plays a mask-collision check between player and enemy. Player wraps around screen via clamp_ip. Collision occasionally fires when sprites are visibly apart — mask still uses pre-clamp position.
Mask Position Source
def collide_mask(left, right):
offset = (right.rect.left - left.rect.left, right.rect.top - left.rect.top)
return left.mask.overlap(right.mask, offset)
Pygame source: mask test uses each sprite’s rect to position the mask. Update rect first.
Correct Order
class Player(pygame.sprite.Sprite):
def update(self):
self.rect.x += self.vx
self.rect.y += self.vy
self.rect.clamp_ip(screen.get_rect())
# later in main loop, AFTER all updates:
if pygame.sprite.collide_mask(player, enemy):
...
Update all sprites; do all rect movements/clamps; then do collision checks. Eliminates stale-position bugs.
Avoid Recomputing Masks
Masks are derived once: pygame.mask.from_surface(self.image). They don’t change with rect position. Only re-derive if the sprite’s image changes (animation frame).
Rotation Caveat
Rotating a sprite changes the image; the mask must be re-derived. After pygame.transform.rotate, recompute mask:
self.image = pygame.transform.rotate(original, angle)
self.mask = pygame.mask.from_surface(self.image)
Verifying
Visual collisions match logical collisions. No false positives when sprites are clearly apart. No false negatives when sprites overlap.
“Mask follows rect. Move rect first, test second.”
For shmups with hundreds of sprites, mask test is O(area). Use rect-rect prefilter, mask-mask only if rect overlap detected — speeds up dramatically.