Coverage for topdownengine/camera.py: 100%

32 statements  

« 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 

5 

6class Camera: 

7 """Class that represents the Camera. 

8 

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 

23 

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

28 

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

30 """Update the screenshake offset. 

31  

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 

41 

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

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

44  

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 ) 

52 

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) 

57 

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

59 """Update the Camera. 

60 

61 Args: 

62 dt (float): The deltatime. 

63 """ 

64 self.update_screenshake(dt) 

65 

66 if self.focus_game_object: 

67 self.track_game_object(dt) 

68 

69 # Bounds 

70 self.handle_bounds() 

71 

72class SmoothTrackerCamera(Camera): 

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

74 

75 def track_game_object(self, dt: float): 

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

77  

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 ) 

85 

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