Building a Real Camera
Follow, Scroll, Shake & Parallax
Pygame has no built-in camera — you build one out of a single offset vector applied to everything you draw. This module covers following the player, smooth scrolling, zoom, screen shake, parallax backgrounds and mini-maps.
The Camera Offset Pattern
A 2D camera is nothing but an (x, y) offset subtracted from every world position before drawing. World objects never move — only the offset changes, creating the illusion of a scrolling world.
class Camera:
def __init__(self, width, height):
self.offset = pygame.Vector2(0, 0)
self.width = width
self.height = height
def apply(self, world_pos):
return world_pos - self.offset
def center_on(self, target_pos):
self.offset.x = target_pos.x - self.width / 2
self.offset.y = target_pos.y - self.height / 2
# in the draw loop:
screen.blit(player_img, camera.apply(player.pos))
screen.blit(enemy_img, camera.apply(enemy.pos))
Following the Player & Smooth Scrolling
Snapping the camera exactly to the player every frame feels rigid. Instead, lerp the camera's offset toward the target position gradually, so it eases into place.
def smooth_follow(self, target_pos, dt, smoothing=5.0):
desired_x = target_pos.x - self.width / 2
desired_y = target_pos.y - self.height / 2
self.offset.x += (desired_x - self.offset.x) * smoothing * dt
self.offset.y += (desired_y - self.offset.y) * smoothing * dt
Zoom & Screen Shake
pygame.transform.scale() it up to fill the window — scaling up = zoom in, scaling down = zoom out.import random
def trigger_shake(camera, duration=0.3, magnitude=8):
camera.shake_timer = duration
camera.shake_magnitude = magnitude
def update_shake(camera, dt):
if camera.shake_timer > 0:
camera.shake_timer -= dt
camera.shake_offset = pygame.Vector2(
random.uniform(-camera.shake_magnitude, camera.shake_magnitude),
random.uniform(-camera.shake_magnitude, camera.shake_magnitude),
)
else:
camera.shake_offset = pygame.Vector2(0, 0)
Parallax Backgrounds
Parallax fakes depth by scrolling distant background layers SLOWER than the foreground — mimicking how distant mountains appear to move less than nearby trees as you travel.
layers = [
{"img": mountains_img, "depth": 0.2},
{"img": trees_img, "depth": 0.5},
]
def draw_parallax(screen, camera, layers):
for layer in layers:
offset_x = -camera.offset.x * layer["depth"]
screen.blit(layer["img"], (offset_x, 0))
World Space vs. Screen Space
Every position in your game exists in two coordinate systems at once: world space, where objects have fixed, permanent coordinates, and screen space, the small visible window into that world. camera.offset is the translation between the two — subtract it from a world position and you get where that object should actually be drawn.
camera.offset is the vector from the world's origin to that window's top-left corner.