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 helpI'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!