FreshRSS

Zobrazení pro čtení

Jsou dostupné nové články, klikněte pro obnovení stránky.

Poor performance with camera zoom

I've created a Camera class that takes a source surface and draws it to a destination surface using the camera's parameters (position, zoom, size, etc.).

The class works well, but the framerate drops significantly when zooming into the surface.

I've tried 2 things:

  • I've added convert_alpha() to the textures, but that doesn't seem to help

  • I've tried using surface.subsurface() to get the portion of the source surface to draw. This does speed things up a bit, but this breaks the camera's behaviour (zooming in is jerky, the source surface isn't centred in the camera when zoomed out).

The Camera class is here: https://github.com/rik-cross/pygamepal/blob/main/src/pygamepal/camera.py

    def draw(self, surface, destSurface):

        # draw border
        pygame.draw.rect(destSurface, self.borderColor, 
                         (self.position[0] - self.borderThickness, self.position[1] - self.borderThickness, 
                          self.size[0] + self.borderThickness * 2, self.size[1] + self.borderThickness * 2), self.borderThickness, border_radius = 1)
        # ensure that the surface is clipped to the camera dimensions
        destSurface.set_clip((self.position[0], self.position[1], self.size[0], self.size[1]))
        # fill the surface to the background color
        destSurface.fill(self.backgroundColor)

        # blit the (zoomed) surface to the destination, and set the target as the center
        x = 0 - (self.size[0] / 2 - self._currentTarget[0] * self._currentZoom)
        y = 0 - (self.size[1] / 2 - self._currentTarget[1] * self._currentZoom)
        # add screen shake
        x += self._shakeCurrent[0]
        y += self._shakeCurrent[1]

        # draw the surface to the destination using the correct position, size, center and zoom
        destSurface.blit(pygame.transform.scale(surface, (surface.get_width() * self._currentZoom, surface.get_height() * self._currentZoom)), 
                         self.position, 
                         (x, y,
                          self.size[0], self.size[1]))
        # reset surface clipping
        destSurface.set_clip()

The example with poor performance is here: https://github.com/rik-cross/pygamepal/blob/main/examples/fullExample.py (walk towards the trees to see the framerate drop).

class GameScene(pygamepal.Scene):
    
    def zoomCamera(self, this, other, zoom):
        self.camera.zoom = zoom
    
    def setChestState(self, this, other, state):
        self.chest.spriteImage.pause = False
        self.chest.spriteImage.state = state

    def init(self):
        
        #
        # add textures
        #

        self.map = pygame.image.load(os.path.join('images', 'map.png'))
        self.map = self.map.convert_alpha()

        #
        # add sprites
        #

        # player

        self.player = Player()
        self.addSprite(self.player)

        # trees

        self.addSprite(
            pygamepal.Sprite(
                imageName = os.path.join('images', 'tree.png'),
                position = (40, 50),
                collider = pygamepal.Collider(offset = (6, 25), size = (12, 5))
            )
        )
        self.addSprite(
            pygamepal.Sprite(
                imageName = os.path.join('images', 'tree.png'),
                position = (70, 40),
                collider = pygamepal.Collider(offset = (6, 25), size = (12, 5))
            )
        )
        self.addSprite(
            pygamepal.Sprite(
                imageName = os.path.join('images', 'tree.png'),
                position = (20, 10),
                collider = pygamepal.Collider(offset = (6, 25), size = (12, 5))
            )
        )

        # chest

        # load a texture
        chestSpritesheet = pygame.image.load(os.path.join('images','chest_spritesheet.png'))
        # split texture into a 2D list of sub-textures
        splitTextures = pygamepal.splitTexture(chestSpritesheet, 48, 48)
        
        self.chest = pygamepal.Sprite(position = (175, 175), size = (16, 14))
        self.chest.collider = pygamepal.Collider(offset = (0, 8), size = (16, 6))
        self.chest.spriteImage = pygamepal.SpriteImage()
        self.chest.spriteImage.addTextures(splitTextures[0][0], splitTextures[0][1], splitTextures[0][2], splitTextures[0][3], offset = (16, 18), state = 'open', animationDelay = 4, loop = False)
        self.chest.spriteImage.addTextures(splitTextures[0][3], splitTextures[0][2], splitTextures[0][1], splitTextures[0][0], offset = (16, 18), state = 'close', animationDelay = 4, loop = False)
        self.chest.spriteImage.pause = True
        self.chest.trigger = pygamepal.Trigger(size = (26, 24), offset = (-5, -5),
            onEnter = lambda this, other, state = 'open': self.setChestState(this, other, state),
            onExit = lambda this, other, state = 'close': self.setChestState(this, other, state))
        self.addSprite(self.chest)

        #
        # customise the scene
        #

        self.backgroundColor = 'black'
        self.sortKey = self.sortByBottom

        #
        # customise the scene camera
        #
        
        self.camera.backgroundColor = 'black'
        self.camera.setTarget(self.player.getCenter(), instant = True)
        self.camera.lazyFollow = 0.9
        self.camera.setZoom(4, instant = True)
        self.camera.lazyZoom = 0.9
        self.camera.clamp = True
        self.camera.clampRect = (0, 0, 256, 256)
        
        #
        # add triggers for the camera
        #

        self.forestTrigger = pygamepal.Trigger(position = (10, 20), size = (100, 70),
            onEnter = lambda this, other, zoom = 6 : self.zoomCamera(this, other, zoom),
            onExit = lambda this, other, zoom = 4 : self.zoomCamera(this, other, zoom))
        self.addTrigger(self.forestTrigger)

        #
        # add map bounds colliders
        #

        # top
        self.addCollider(pygamepal.Collider((0, 0), (256, 2)))
        # bottom
        self.addCollider(pygamepal.Collider((0, 254), (256, 2)))
        # left
        self.addCollider(pygamepal.Collider((0, 0), (2, 256)))
        # right
        self.addCollider(pygamepal.Collider((254, 0), (2, 256)))

    def update(self):

        # [ESC] to return to menu scene
        if self.game.input.isKeyPressed(pygame.K_ESCAPE):
            self.game.currentScene = menuScene
        
        # camera tracks the player
        self.camera.target = self.player.getCenter()

    def draw(self):

        self.sceneSurface.blit(self.map, (0, 0))

I thought I'd ask for help in case there's an easy fix!

Thanks!

❌