Coverage for topdownengine/scenes.py: 44%
48 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
2from .math import scale_rect
4class BaseScene:
5 """A scene that determines the behavior of the game loop.
7 Attributes:
8 game (Game): The game object to use.
9 ui_containers (list[UIContainer]): The list of UIContainers for this scene.
10 """
11 def __init__(self, game):
12 """Initialize the scene.
14 Args:
15 game (Game): The game object to use.
16 """
17 self.game = game
18 self.ui_containers = []
20 def handle_event(self, event: pg.Event):
21 """Handle a single event.
23 Args:
24 event (pygame.Event): The event to handle.
25 """
26 for container in self.ui_containers:
27 container.handle_event(event)
29 def update(self, dt: float):
30 """Update the scene.
32 Args:
33 dt (float): The deltatime.
34 """
35 for container in self.ui_containers:
36 container.update(dt)
38 def render(self, surface: pg.Surface):
39 """Render the scene.
41 Args:
42 surface (pygame.Surface): The Surface to render to.
43 """
44 for container in self.ui_containers:
45 container.render(surface)
47class GameplayScene(BaseScene):
48 """A scene that updates and renders all `GameObject` instances.
50 Attributes:
51 global_alpha (int): The global alpha for lighting.
52 """
54 def __init__(self, game):
55 """Initialize the GameplayScene.
57 Args:
58 game (Game): The game object to use.
59 """
60 super().__init__(game)
61 self.global_alpha = 0
62 self._light_filters = {}
64 def get_light(self, radius: int):
65 """Get the cached light surface of a given radius. One will be created and stored if it doesn't already exist.
67 Args:
68 radius (int): The radius of the light.
69 """
70 if not radius in self._light_filters:
71 self._light_filters[radius] = self.create_light(radius)
72 return self._light_filters[(radius)]
74 def create_light(self, radius: int):
75 """Create a light surface for a given radius.
77 Args:
78 radius (int): The radius of the new light.
79 """
80 surface = pg.Surface((max(radius * 2, 1), max(radius * 2, 1)), pg.SRCALPHA)
81 for i in range(int(radius), 0, -1):
82 alpha = 255 * (1 - i / radius)
83 pg.draw.circle(surface, (0, 0, 0, alpha), (radius, radius), i)
84 return surface
86 def update(self, dt):
87 """Update the GameplayScene.
89 Args:
90 dt (float): The deltatime.
91 """
92 super().update(dt)
93 self.game.game_object_group.update(dt, self.game)
95 def render(self, surface: pg.Surface):
96 """Render the GameplayScene.
98 Args:
99 surface (pygame.Surface): The Surface to render to.
100 """
101 overlay = pg.Surface(surface.size, pg.SRCALPHA)
102 overlay.fill((0, 0, 0, self.global_alpha))
104 for game_object in sorted(self.game.game_object_group.game_objects, key=lambda g: g.draw_index):
105 cr = game_object.rect.move(-self.game.camera.position * game_object.SCALE)
106 surface.blit(game_object.image, cr)
107 if self.global_alpha > 0 and game_object.light_radius > 0:
108 scaled_lr = game_object.light_radius * game_object.SCALE
109 overlay.blit(
110 self.get_light(scaled_lr),
111 (
112 cr.centerx - scaled_lr,
113 cr.bottom - scaled_lr - game_object.z * game_object.SCALE - game_object.current_frame.height / 2 + game_object.elevation * game_object.SCALE
114 ),
115 special_flags=pg.BLEND_RGBA_SUB
116 )
118 # Draw debug in a separate loop so that it is drawn over images.
119 if self.game.debug:
120 for game_object in self.game.game_object_group.game_objects:
121 # Draw Colliders
122 for collider in game_object.world_colliders:
123 pg.draw.rect(
124 surface,
125 (0, 0, 255),
126 scale_rect(collider.move(-self.game.camera.position), game_object.SCALE),
127 1
128 )
130 surface.blit(overlay, (0, 0))
132 super().render(surface) # Draw UI