Coverage for topdownengine/camera.py: 100%
32 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-13 14:20 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-13 14:20 +0000
1import pygame as pg
2import random
3from .game_object import GameObject
4from .game import Game
6class Camera:
7 """Class that represents the Camera.
9 Attributes:
10 real_position (pygame.Vector2): The position of the camera, excluding screenshake.
11 screenshake_offset (pygame.Vector2): The current screenshake offset.
12 focus_game_object (GameObject|None): The GameObject to center the camera on. If None, the Camera will not center on any GameObject.
13 screenshake (dict[str,float]): Current screenshake dictionary.
14 position (pygame.Vector2): The position of the camera, factoring screenshake.
15 """
16 def __init__(self, game: Game):
17 "Initialize the camera."
18 self.real_position = pg.Vector2()
19 self.screenshake_offset = pg.Vector2()
20 self.focus_game_object = None
21 self.screenshake = {"duration": 0, "intensity": 0}
22 self.game = game
24 @property
25 def position(self) -> pg.Vector2:
26 "Get the position of the camera with screenshake."
27 return self.real_position + (self.screenshake_offset if self.screenshake["duration"] > 0 else pg.Vector2())
29 def update_screenshake(self, dt: float) -> None:
30 """Update the screenshake offset.
32 Args:
33 dt (float): The deltatime
34 """
35 if self.screenshake["duration"] > 0:
36 self.screenshake_offset = pg.Vector2(
37 random.uniform(-self.screenshake["intensity"], self.screenshake["intensity"]),
38 random.uniform(-self.screenshake["intensity"], self.screenshake["intensity"]),
39 )
40 self.screenshake["duration"] = max(0, self.screenshake["duration"] - (dt/1000)) # dt is in milliseconds
42 def track_game_object(self, dt: float) -> None:
43 """Tracks the `focus_game_object` by snapping instantly to it.
45 Args:
46 dt (float): The deltatime
47 """
48 self.real_position = pg.Vector2(self.focus_game_object.position) - pg.Vector2(
49 self.game.screen.width / GameObject.SCALE / 2,
50 self.game.screen.height / GameObject.SCALE / 2
51 )
53 def handle_bounds(self) -> None:
54 "Handle camera bounds."
55 self.real_position.x = max(0, self.position.x)
56 self.real_position.y = max(0, self.position.y)
58 def update(self, dt: float) -> None:
59 """Update the Camera.
61 Args:
62 dt (float): The deltatime.
63 """
64 self.update_screenshake(dt)
66 if self.focus_game_object:
67 self.track_game_object(dt)
69 # Bounds
70 self.handle_bounds()
72class SmoothTrackerCamera(Camera):
73 "A subclass of Camera with smoother tracking logic."
75 def track_game_object(self, dt: float):
76 """Tracks the `focus_game_object` by smoothly moving to its position.
78 Args:
79 dt (float): The deltatime
80 """
81 target_position = pg.Vector2(self.focus_game_object.position) - pg.Vector2(
82 self.game.screen.width / GameObject.SCALE / 2,
83 self.game.screen.height / GameObject.SCALE / 2
84 )
86 self.real_position += (target_position - self.real_position) * (dt / 1000) * 5