Quick answer: Replace pygame.sprite.Group with pygame.sprite.LayeredUpdates. Set each sprite’s _layer attribute (or pass layer to add). For top-down y-sort, update _layer = self.rect.y each tick.
Player walks past a tree. Some frames the player draws on top of the tree; other frames the tree draws on top of the player. Pygame’s default Group has no z-order; it draws in dict-order which is essentially random.
The Symptom
Overlapping sprites alternate which is in front. Spawning a new sprite changes the draw order for all existing ones. Top-down view feels wrong — near sprites disappear behind far ones.
The Fix
Step 1: Use LayeredUpdates.
import pygame
GROUND, ITEMS, ENTITIES, UI = 0, 10, 20, 100
all_sprites = pygame.sprite.LayeredUpdates()
floor = make_floor_sprite()
all_sprites.add(floor, layer=GROUND)
coin = make_coin_sprite()
all_sprites.add(coin, layer=ITEMS)
player = make_player_sprite()
all_sprites.add(player, layer=ENTITIES)
Floor draws first (lowest layer), then items, then entities. Insertion order within a layer is preserved.
Step 2: Top-down y-sort.
class YSortedSprite(pygame.sprite.Sprite):
def update(self, *args):
# move logic ...
all_sprites.change_layer(self, self.rect.bottom)
Each frame, set the layer to the sprite’s bottom-y. Players and trees sort correctly: a tree with higher bottom-y draws on top of objects above it on screen.
Step 3: Draw the group.
all_sprites.update(dt)
all_sprites.draw(screen)
LayeredUpdates.draw respects layer order automatically.
Performance
change_layer is O(N log N) per call. For hundreds of sprites doing it every frame is fine; thousands gets noticeable. Profile if you hit limits and consider a fixed-z scheme with occasional resort.
Verifying
Walk the player past a tree. Player’s feet below tree base = player draws in front. Above = tree draws in front. With Group: random. With LayeredUpdates: consistent.
“LayeredUpdates. Layer per role. Or y-sort for top-down. Order stays right.”
Related Issues
For pygame mixer cuts, see channel overflow. For tick busy loop, see tick CPU.
Layer per role. y-sort for depth. No flicker.