pythonpygamegame-development

Artifacts with pygame when trying to update visible sprites only


I'm learning the basics of the pygame library and already struggling. The "game" at this point only has a player and walls. There are 2 main surfaces: "world" (the actual game map) and "screen" (which serves as a viewport for "view_src" w/ scaling & scrolling, "viewport" is the corresponding rect).

Here's the problem: I want to implement at least basic optimisation and only render sprites that are actually visible, so I'm filtering the "all" group to whatever collides with the viewport. That acts as expected. But when I call the rendering functions on the "visible" ad hoc group I get artifacts whereas calling them on "all" works just fine.

Here's the relevant snippet from the game loop:

        # clear old sprites
        all.clear(world, background) # this should clear the OLD position of all sprites, right?


        # handle input and generic game logic here
        if player.move(key_state, walls) != (0,0): # moves the player's rect if possible
            scroll_view(world, player.last_move, view_src) # shifts view_src if applicable


        # this does very little and should be unrelated to the issue
        all.update()


        # draw the new scene
        visible = pg.sprite.Group([ spr for spr in all.sprites() if view_src.colliderect(spr.rect) ])
        print(visible.sprites()) # confirms the visible sprites are chosen correctly
        visible.draw(world) # results in drawing each sprite in its new AND old position
        #all.draw(world) # acts as it should if used instead

        scaled = pg.transform.scale(world.subsurface(view_src), viewport.size)
        screen.blit(scaled, viewport.topleft)
        pg.display.flip()

(I do .empty() the "visible" group at the end of the loop)

Even if I determine "visible" earlier and call visible.clear(world, background) and then go all.draw(world) I get the exact same issue, it only works if both .clear() and .draw() are called on "all". This is already after consulting an AI which told me this works just fine so hopefully a good old fashioned human can point me in the right direction.


Solution

  • Found the problem and the fix thanks to Kingsley's nudge.

    The issue:

    Group.clear() clears the sprites drawn by the last .draw() of that exact same group. So using a different group for .clear() and .draw() doesn't work, and the continuity it needs to function is also lost by re-assigning the "visible" group each time.

    The solution:

    Initialise "visible" before the loop, persist it between iterations and add/remove sprites as needed.

    Fixed code:

            # clear old sprites
            visible.clear(world, background) # clears sprites from the last .draw()
    
            # handle input and generic game logic here
            if player.move(key_state, walls) != (0,0):
                scroll_view(world, player, view_src)
    
            # "step event", update positions etc here
            all.update()
    
            # draw the new scene
            visible.empty()
            visible.add([ spr for spr in all if view_src.colliderect(spr.rect) ])
            visible.draw(world)
            render_view(screen, world, view_src, viewport) # this is still what it was before