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.