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