Coverage for topdownengine/camera.py: 88%

32 statements  

« 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 

4 

5class Camera: 

6 """Class that represents the Camera. 

7 

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} 

21 

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()) 

26 

27 def update_screenshake(self, dt: float) -> None: 

28 """Update the screenshake offset. 

29  

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 

39 

40 def track_game_object(self, dt: float) -> None: 

41 """Tracks the `focus_game_object` by snapping instantly to it. 

42  

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 ) 

51 

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) 

56 

57 def update(self, dt: float) -> None: 

58 """Update the Camera. 

59 

60 Args: 

61 dt (float): The deltatime. 

62 """ 

63 self.update_screenshake(dt) 

64 

65 if self.focus_game_object: 

66 self.track_game_object(dt) 

67 

68 # Bounds 

69 self.handle_bounds() 

70 

71class SmoothTrackerCamera(Camera): 

72 "A subclass of Camera with smoother tracking logic." 

73 

74 def track_game_object(self, dt: float): 

75 """Tracks the `focus_game_object` by smoothly moving to its position. 

76  

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 ) 

85 

86 self.real_position += (target_position - self.real_position) * (dt / 1000) * 5