camera.py

Camera System - Advanced 2D Camera with Smooth Movement, Effects, and Parallax

LOCATION: lunaengine/graphics/camera.py

DESCRIPTION:
A flexible, high‑performance 2D camera system for LunaEngine. Features:
- Unified coordinate system: camera.position = center of viewport
- Strategy‑based follow modes (extensible via FollowStrategy)
- Smooth interpolation with multiple easing types
- Constraints (zoom limits, world boundaries)
- Stackable effects (shake, trauma, fade, etc.)
- Optimised parallax backgrounds with tile support

LIBRARIES USED:
- pygame: Vector2, Rect, Surface operations
- numpy: Random for shake, vectorized operations (optional)
- abc, enum, dataclasses, typing

class CameraMode

Description

Legacy follow modes – kept for backward compatibility.
Internally mapped to FollowStrategy instances.

Attributes
FIXED: Any = 'fixed'
FOLLOW: Any = 'follow'
PLATFORMER: Any = 'platformer'
TOPDOWN: Any = 'topdown'
Methods

No methods defined.

class CameraShakeType

Description

Type of camera shake effect.

Attributes
POSITIONAL: Any = 'positional'
ROTATIONAL: Any = 'rotational'
TRAUMA: Any = 'trauma'
Methods

No methods defined.

class InterpolationType

Description

Easing functions for smooth camera movement.

Attributes
LINEAR: Any = 'linear'
SMOOTHSTEP: Any = 'smoothstep'
QUADRATIC_IN: Any = 'quadratic_in'
QUADRATIC_OUT: Any = 'quadratic_out'
QUADRATIC_IN_OUT: Any = 'quadratic_in_out'
CUBIC_IN: Any = 'cubic_in'
CUBIC_OUT: Any = 'cubic_out'
CUBIC_IN_OUT: Any = 'cubic_in_out'
Methods

No methods defined.

class FollowStrategy

Description

Abstract base class for camera follow behaviours.

Methods
def get_target_position(self: Any, camera: 'Camera', dt: float) -> pygame.Vector2
Return the desired world position of the camera (center).

class SimpleFollow

Description

Follow the target's world position directly.

Methods
def get_target_position(self: Any, camera: 'Camera', dt: float) -> pygame.Vector2
No documentation

class FixedFollow

Description

Keep the camera at a fixed world position, ignoring the target.

Methods
def __init__(self: Any, fixed_position: Union[Tuple[float, float], pygame.Vector2]) -> Any
No documentation
def get_target_position(self: Any, camera: 'Camera', dt: float) -> pygame.Vector2
No documentation

class PlatformerFollow

Description

Platformer-style camera with a deadzone in screen space.
The camera moves only when the target leaves the deadzone rectangle.

Methods
def __init__(self: Any, deadzone_rect: Optional[pygame.Rect] = None) -> Any
No documentation
def get_target_position(self: Any, camera: 'Camera', dt: float) -> pygame.Vector2
No documentation

class TopDownFollow

Description

Top-down RPG style camera with look‑ahead based on target velocity/direction.

Methods
def __init__(self: Any, lead_factor: float = 0.3) -> Any
No documentation
def get_target_position(self: Any, camera: 'Camera', dt: float) -> pygame.Vector2
No documentation

class CameraConstraints

Description

Limits applied to camera movement and zoom.

Attributes
min_zoom: float = 0.1
max_zoom: float = 10.0
bounds: Optional[pygame.Rect] = None
rotation_range: Tuple[float, float] = (-180.0, 180.0)
Methods

No methods defined.

class CameraEffect

Description

Base class for temporary camera effects (shake, fade, flash, etc.).

Methods
def update(self: Any, dt: float) -> bool
Update effect state. Return True if still active, False when finished.
def apply(self: Any, camera: 'Camera') -> None
Modify the camera's offset, rotation, zoom, etc.

class ShakeEffect

Description

Positional or rotational shake with intensity decay.

Methods
def __init__(self: Any, intensity: float = 1.0, duration: float = 0.5, shake_type: CameraShakeType = CameraShakeType.POSITIONAL) -> Any
No documentation
def update(self: Any, dt: float) -> bool
No documentation
def apply(self: Any, camera: 'Camera') -> None
No documentation

class TraumaEffect

Description

Trauma‑based shake – intensity scales with trauma^2.

Methods
def __init__(self: Any, trauma: float = 1.0, decay_rate: float = 1.5) -> Any
No documentation
def update(self: Any, dt: float) -> bool
No documentation
def apply(self: Any, camera: 'Camera') -> None
No documentation

class ParallaxSprite

Description

A single sprite inside a parallax layer.

Attributes:
   surface: The image to draw.
   base_pos: World position relative to the layer's origin (in world units).
   scale: Uniform scale factor (1.0 = original size).
   color: RGB colour multiplier (1.0,1.0,1.0 = normal).
   alpha: Opacity (0.0 = transparent, 1.0 = opaque).
   angle: Rotation in degrees (0 = no rotation).
   oscillate_x: Sine wave amplitude for X movement (world units).
   oscillate_y: Sine wave amplitude for Y movement.
   oscillate_speed: Oscillation frequency (cycles per second).
   phase_offset: Initial phase offset (radians) – randomise for variety.
   brightness_pulse: Amplitude of brightness pulsation (0..1).
   brightness_speed: Pulsation speed (cycles per second).

Attributes
surface: pygame.Surface = None
base_pos: pygame.Vector2 = field(default_factory=lambda: pygame.Vector2(0, 0))
_cache: Dict[str, Tuple[float, float, Tuple[int, int, int]]] = field(default_factory=dict, init=False, repr=False)
scale: float = 1.0
color: Tuple[float, float, float] = (1.0, 1.0, 1.0)
alpha: float = 1.0
angle: float = 0.0
oscillate_x: float = 0.0
oscillate_y: float = 0.0
oscillate_speed: float = 1.0
phase_offset: float = 0.0
brightness_pulse: float = 0.0
brightness_speed: float = 1.0
wind_movement: bool = True
wind_intensity: float = 0.0
Methods
def __post_init__(self: Any) -> Any
No documentation
def get_animated_offset(self: Any, time: float) -> pygame.Vector2
No documentation
def get_current_brightness(self: Any, time: float) -> float
Return a brightness multiplier (0..1) pulsating over time.
def get_transformed_surface(self: Any, size: Tuple[int, int], angle: float, colour: Tuple[float, float, float, float]) -> pygame.Surface
Return a cached transformed version of the sprite surface.
size: (width, height) in pixels after scaling
angle: rotation in degrees
colour: (r,g,b,a) multipliers (each 0..1)

class ParallaxLayer

Description

A scrollable layer that can contain:
 - A single tiled texture (simple & efficient)
 - A list of individual ParallaxSprites (flexible)

If `tile_texture` is set, the layer ignores the sprite list and renders tiled.
Otherwise, it renders the sprite list.

Methods
def __init__(self: Any, speed: Tuple[float, float] = (1.0, 1.0), repeat_x: bool = True, repeat_y: bool = True, z_index: int = 0) -> Any
Args:
   speed: Scroll multiplier (1.0 = moves with camera, 0.0 = fixed in background)
   repeat_x, repeat_y: Only used for tiled texture mode.
   z_index: Render order (lower = behind).
def set_tiled_texture(self: Any, texture: pygame.Surface) -> Any
Switch this layer to tiled rendering using a single texture.
def add_sprite(self: Any, sprite: ParallaxSprite) -> Any
Add an individual sprite to this layer (automatically disables tiled mode).
def add_sprites(self: Any, sprites: List[ParallaxSprite]) -> Any
No documentation
def clear_sprites(self: Any) -> Any
No documentation
def populate_random(self: Any, surface: pygame.Surface, count: int, area: pygame.Rect, scale_range: Tuple[float, float] = (1.0, 1.0), speed_range: Tuple[float, float] = (0.0, 0.0), oscillate_x_range: Tuple[float, float] = (0.0, 0.0), oscillate_y_range: Tuple[float, float] = (0.0, 0.0), oscillate_speed_range: Tuple[float, float] = (0.5, 2.0), brightness_pulse_range: Tuple[float, float] = (0.0, 0.0), brightness_speed_range: Tuple[float, float] = (0.5, 2.0), alpha_range: Tuple[float, float] = (1.0, 1.0), angle_range: Tuple[float, float] = (0.0, 0.0), seed: Optional[int] = None) -> Any
Fill the layer with randomly placed sprites using the same surface.

Args:
   surface: The image to use for all sprites.
   count: Number of sprites to create.
   area: World rectangle (x, y, width, height) where sprites will be placed.
   scale_range: (min, max) uniform scale.
   oscillate_x_range: (min, max) oscillation amplitude in X (world units).
   oscillate_y_range: (min, max) oscillation amplitude in Y.
   oscillate_speed_range: (min, max) oscillation frequency (Hz).
   brightness_pulse_range: (min, max) brightness pulsation amplitude.
   brightness_speed_range: (min, max) pulsation speed (Hz).
   alpha_range: (min, max) opacity.
   angle_range: (min, max) rotation angle (degrees).
   seed: Optional random seed for reproducibility.

class ParallaxBackground

Description

Manager for multiple parallax layers.
Provides global time for animations and brightness pulsations.

Methods
def __init__(self: Any, camera: 'Camera') -> Any
No documentation
def add_layer(self: Any, speed: Tuple[float, float] = (1.0, 1.0), repeat_x: bool = True, repeat_y: bool = True, z_index: int = 0) -> ParallaxLayer
Create and return a new empty layer.
def remove_layer(self: Any, layer: ParallaxLayer) -> Any
No documentation
def clear(self: Any) -> Any
No documentation
def update(self: Any, dt: float) -> Any
Update global animation time and layer scroll offsets.
Call this once per frame from Camera.update().
def render(self: Any, renderer: OpenGLRenderer, viewport: Optional[pygame.Rect] = None) -> Any
Render all layers onto the screen.
Each layer either renders a tiled texture or a list of animated sprites.
def _render_tiled(self: Any, renderer: OpenGLRenderer, layer: ParallaxLayer, viewport: pygame.Rect) -> Any
Efficient tiled rendering (same as before).
def _render_sprites(self: Any, renderer: OpenGLRenderer, layer: ParallaxLayer, viewport: pygame.Rect) -> Any
Render individual sprites with animation and brightness.

class Camera

Description

Advanced 2D Camera with unified coordinate system (position = viewport center).
Supports multiple follow strategies, constraints, effects, and parallax.

Methods
def __init__(self: Any, scene: 'Scene', width: int, height: int) -> Any
No documentation
def position(self: Any) -> pygame.Vector2
Camera position with active shake offset applied.
def position(self: Any, value: Any) -> Any
Set the base camera position (center of viewport).
def base_position(self: Any) -> pygame.Vector2
Base camera position without shake offset.
def mode(self: Any) -> CameraMode
Legacy: get current follow mode (maps to strategy).
def mode(self: Any, value: CameraMode) -> Any
Legacy: set follow mode and update strategy accordingly.
def viewport_rect(self: Any) -> pygame.Rect
World rectangle visible through the camera (topleft in world coordinates).
def set_target(self: Any, target: Any, mode: Optional[CameraMode] = None) -> Any
Set the camera target. If mode is provided, it updates the follow strategy.
def set_follow_strategy(self: Any, strategy: FollowStrategy) -> Any
Set a custom follow strategy.
def set_bounds(self: Any, bounds: pygame.Rect) -> Any
Set world boundaries (legacy).
def set_zoom(self: Any, zoom: float, smooth: bool = True) -> Any
Set target zoom level.
def shake(self: Any, intensity: float = 1.0, duration: float = 0.5, shake_type: CameraShakeType = CameraShakeType.POSITIONAL) -> Any
Start a camera shake effect.
def add_trauma(self: Any, amount: float) -> Any
Add trauma (if a TraumaEffect exists, increase it; otherwise create one).
def add_effect(self: Any, effect: CameraEffect) -> Any
Add a new camera effect (will be updated/removed automatically).
def remove_effect(self: Any, effect: CameraEffect) -> Any
Remove a specific effect.
def clear_effects(self: Any) -> Any
Remove all active effects.
def update(self: Any, dt: float) -> Any
Update camera position, zoom, effects, and parallax.
def _apply_smooth_movement(self: Any, dt: float) -> Any
Interpolate _position towards target_position.
def _apply_smooth_zoom(self: Any, dt: float) -> Any
Interpolate zoom towards target_zoom.
def _update_effects(self: Any, dt: float) -> Any
Update all active effects and remove finished ones.
def _apply_constraints(self: Any) -> Any
Apply world bounds and zoom limits.
def _update_viewport_rect(self: Any) -> Any
Recalculate viewport_rect based on current position and zoom.
def _update_renderer_camera_position(self: Any) -> Any
Push camera position to the renderer.
def world_to_screen(self: Any, world_pos: Union[Tuple[float, float], pygame.Vector2, List[float], np.ndarray]) -> pygame.Vector2
Convert world coordinates to screen pixels (top‑left origin).
def world_to_screen_list(self: Any, world_positions: List, return_type: str = 'list') -> Any
Batch conversion for lists of world positions.
def screen_to_world(self: Any, screen_pos: Union[Tuple[float, float], pygame.Vector2, List[float], np.ndarray]) -> pygame.Vector2
Convert screen pixel coordinates to world coordinates.
def screen_to_world_list(self: Any, screen_positions: List) -> List[pygame.Vector2]
Batch conversion for lists of screen positions.
def screen_to_world_vector(self: Any, screen_vec: Union[Tuple[float, float], pygame.Vector2]) -> pygame.Vector2
Convert a screen vector (e.g., movement) to world vector (ignores viewport offset).
def convert_size_zoom(self: Any, size: Union[Tuple[float, float], List[float], float, pygame.Vector2]) -> Any
Scale a size from world units to screen pixels, or vice‑versa.
def convert_size_zoom_list(self: Any, sizes: List, return_type: str = 'list') -> Any
Batch version of convert_size_zoom.
def get_visible_rect(self: Any) -> pygame.Rect
Alias for viewport_rect.
def is_visible(self: Any, world_pos: Union[Tuple[float, float], pygame.Vector2], margin: float = 0.0) -> bool
Check if a world point is inside the viewport (plus optional margin).
def get_transform_matrix(self: Any) -> Any
No documentation
def get_view_projection_matrix(self: Any, width: Optional[int] = None, height: Optional[int] = None, column_major: bool = False) -> np.ndarray
Return a 4x4 view-projection matrix that maps world coordinates to clip space
(normalized device coordinates, range [-1, 1] on both axes, Y up).

The matrix is constructed as: clip = S * R * T
where:
   T = translation by -camera position
   R = rotation by -camera.rotation (around the camera center)
   S = scale by (2 * zoom / viewport_width, 2 * zoom / viewport_height)

Args:
   width:  Viewport width in pixels. If None, uses self.viewport_width.
   height: Viewport height in pixels. If None, uses self.viewport_height.
   column_major: If True, return the matrix in column‑major order (for OpenGL).
               Otherwise, return row‑major.

Returns:
   A 4x4 numpy array of float32.
def _get_target_position(self: Any) -> pygame.Vector2
Extract the world position of the current target (if any).
def add_parallax_layer(self: Any, surface: pygame.Surface, speed_factor: float, tile_mode: bool = True, offset: Tuple[float, float] = (0, 0)) -> ParallaxLayer
Add a new parallax layer to the camera.
def remove_parallax_layer(self: Any, layer: ParallaxLayer) -> Any
Remove a parallax layer.
def clear_parallax_layers(self: Any) -> Any
Remove all parallax layers.
def render_parallax(self: Any, renderer: Any) -> None
Render the parallax background.
def enable_parallax(self: Any, enabled: bool = True) -> Any
Enable/disable parallax rendering.
def get_parallax_layer_count(self: Any) -> int
Number of active parallax layers.

Global Functions

def _interpolate_linear(start: float, end: float, t: float) -> float

No documentation

def _interpolate_smoothstep(start: float, end: float, t: float) -> float

No documentation

def _interpolate_quadratic_in(start: float, end: float, t: float) -> float

No documentation

def _interpolate_quadratic_out(start: float, end: float, t: float) -> float

No documentation

def _interpolate_quadratic_in_out(start: float, end: float, t: float) -> float

No documentation

def _interpolate_cubic_in(start: float, end: float, t: float) -> float

No documentation

def _interpolate_cubic_out(start: float, end: float, t: float) -> float

No documentation

def _interpolate_cubic_in_out(start: float, end: float, t: float) -> float

No documentation

def interpolate(start: Union[float, pygame.Vector2], end: Union[float, pygame.Vector2], t: float, itype: InterpolationType = InterpolationType.LINEAR) -> Union[float, pygame.Vector2]

Interpolate between start and end using the specified easing function.

Back to Graphics Module