batframework 1.0.9a7__py3-none-any.whl → 1.0.9a9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. batFramework/__init__.py +20 -11
  2. batFramework/action.py +1 -1
  3. batFramework/animatedSprite.py +47 -116
  4. batFramework/animation.py +30 -5
  5. batFramework/audioManager.py +8 -5
  6. batFramework/baseScene.py +240 -0
  7. batFramework/camera.py +4 -0
  8. batFramework/constants.py +6 -2
  9. batFramework/cutscene.py +221 -21
  10. batFramework/cutsceneManager.py +5 -2
  11. batFramework/drawable.py +7 -5
  12. batFramework/easingController.py +10 -11
  13. batFramework/entity.py +21 -2
  14. batFramework/enums.py +48 -33
  15. batFramework/gui/__init__.py +6 -3
  16. batFramework/gui/animatedLabel.py +10 -2
  17. batFramework/gui/button.py +4 -31
  18. batFramework/gui/clickableWidget.py +63 -50
  19. batFramework/gui/constraints/constraints.py +212 -136
  20. batFramework/gui/container.py +77 -58
  21. batFramework/gui/debugger.py +12 -17
  22. batFramework/gui/draggableWidget.py +21 -17
  23. batFramework/gui/image.py +3 -10
  24. batFramework/gui/indicator.py +56 -1
  25. batFramework/gui/interactiveWidget.py +127 -108
  26. batFramework/gui/label.py +73 -64
  27. batFramework/gui/layout.py +286 -445
  28. batFramework/gui/meter.py +42 -20
  29. batFramework/gui/radioButton.py +20 -69
  30. batFramework/gui/root.py +99 -29
  31. batFramework/gui/selector.py +250 -0
  32. batFramework/gui/shape.py +13 -5
  33. batFramework/gui/slider.py +262 -107
  34. batFramework/gui/syncedVar.py +49 -0
  35. batFramework/gui/textInput.py +46 -22
  36. batFramework/gui/toggle.py +70 -52
  37. batFramework/gui/tooltip.py +30 -0
  38. batFramework/gui/widget.py +222 -135
  39. batFramework/manager.py +7 -8
  40. batFramework/particle.py +4 -1
  41. batFramework/propertyEaser.py +79 -0
  42. batFramework/renderGroup.py +17 -50
  43. batFramework/resourceManager.py +43 -13
  44. batFramework/scene.py +15 -335
  45. batFramework/sceneLayer.py +138 -0
  46. batFramework/sceneManager.py +31 -36
  47. batFramework/scrollingSprite.py +8 -3
  48. batFramework/sprite.py +1 -1
  49. batFramework/templates/__init__.py +1 -2
  50. batFramework/templates/controller.py +97 -0
  51. batFramework/timeManager.py +76 -22
  52. batFramework/transition.py +37 -103
  53. batFramework/utils.py +125 -66
  54. {batframework-1.0.9a7.dist-info → batframework-1.0.9a9.dist-info}/METADATA +24 -3
  55. batframework-1.0.9a9.dist-info/RECORD +67 -0
  56. {batframework-1.0.9a7.dist-info → batframework-1.0.9a9.dist-info}/WHEEL +1 -1
  57. batFramework/character.py +0 -27
  58. batFramework/templates/character.py +0 -43
  59. batFramework/templates/states.py +0 -166
  60. batframework-1.0.9a7.dist-info/RECORD +0 -63
  61. /batframework-1.0.9a7.dist-info/LICENCE → /batframework-1.0.9a9.dist-info/LICENSE +0 -0
  62. {batframework-1.0.9a7.dist-info → batframework-1.0.9a9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,138 @@
1
+ from __future__ import annotations
2
+ import batFramework as bf
3
+ import pygame
4
+ from .entity import Entity
5
+ from .drawable import Drawable
6
+
7
+ from typing import TYPE_CHECKING, Any
8
+ if TYPE_CHECKING:
9
+ from .baseScene import BaseScene
10
+
11
+ class SceneLayer:
12
+ """
13
+ A scene layer is a 'dimension' bound to a scene
14
+ Each layer contains its own entities and camera
15
+ One common use would be to separate GUI and game into two separate layers
16
+ """
17
+ def __init__(self,name:str,convert_alpha:bool = False):
18
+ self.scene = None
19
+ self.name = name
20
+ self.entities : dict[int,Entity] = {}
21
+ self.entities_to_add : set[Entity]= set()
22
+ self.entities_to_remove : set[Entity]= set()
23
+ self.draw_order : list[int] = []
24
+ self.camera = bf.Camera(convert_alpha=convert_alpha)
25
+
26
+ def set_clear_color(self,color):
27
+ self.camera.set_clear_color(color)
28
+
29
+ def set_scene(self, scene:BaseScene):
30
+ self.scene = scene
31
+
32
+ def add(self,*entities:Entity):
33
+ for e in entities:
34
+ if e.uid not in self.entities and e not in self.entities_to_add:
35
+ self.entities_to_add.add(e)
36
+
37
+
38
+ def get_by_tags(self,*tags)->list[Entity]:
39
+ return [v for v in self.entities.values() if v.has_tags(*tags) ]
40
+
41
+ def get_by_uid(self,uid:int)->Entity|None:
42
+ return self.entities.get(uid,None)
43
+
44
+ def remove(self,*entities:Entity):
45
+ for e in entities:
46
+ if e.uid in self.entities and e not in self.entities_to_remove:
47
+ self.entities_to_remove.add(e)
48
+
49
+ def process_event(self,event:pygame.Event):
50
+ if event.type == pygame.VIDEORESIZE and not pygame.SCALED & bf.const.FLAGS:
51
+ self.camera.set_size(bf.const.RESOLUTION)
52
+
53
+ for e in self.entities.values():
54
+ e.process_event(event)
55
+ if event.consumed : return
56
+
57
+ def update(self, dt):
58
+ # Update all entities
59
+ for e in self.entities.values():
60
+ e.update(dt)
61
+
62
+ self.flush_entity_changes()
63
+
64
+ # Update the camera
65
+ self.camera.update(dt)
66
+
67
+ def flush_entity_changes(self):
68
+ """
69
+ Synchronizes entity changes by removing entities marked for removal,
70
+ adding new entities, and updating the draw order if necessary.
71
+ """
72
+
73
+
74
+ # Remove entities marked for removal
75
+ for e in self.entities_to_remove:
76
+ if e.uid in self.entities.keys():
77
+ e.set_parent_scene(None)
78
+ self.entities.pop(e.uid)
79
+ self.entities_to_remove.clear()
80
+
81
+ # Add new entities
82
+ reorder = False
83
+ for e in self.entities_to_add:
84
+ self.entities[e.uid] = e
85
+ e.set_parent_layer(self)
86
+ e.set_parent_scene(self.scene)
87
+ if not reorder and isinstance(e, Drawable):
88
+ reorder = True
89
+ self.entities_to_add.clear()
90
+
91
+ # Reorder draw order if necessary
92
+ if reorder:
93
+ self.update_draw_order()
94
+
95
+ def clear(self):
96
+ """
97
+ Clear the camera surface
98
+ """
99
+ self.camera.clear()
100
+
101
+ def draw(self, surface: pygame.Surface):
102
+ self.camera.clear()
103
+ debugMode = bf.ResourceManager().get_sharedVar("debug_mode")
104
+ # Draw entities in the correct order
105
+ for uid in self.draw_order:
106
+ if uid in self.entities and not self.entities[uid].drawn_by_group: # Ensure the entity still exists
107
+ self.entities[uid].draw(self.camera)
108
+
109
+ # Draw debug outlines if in debug mode
110
+ if debugMode == bf.debugMode.OUTLINES:
111
+ [self.debug_entity(uid) for uid in self.draw_order if uid in self.entities]
112
+
113
+ # Blit the camera surface onto the provided surface
114
+ # surface.blit(self.camera.surface, (0, 0))
115
+ self.camera.draw(surface)
116
+
117
+ def update_draw_order(self):
118
+ self.draw_order = sorted(
119
+ (k for k,v in self.entities.items() if isinstance(v,Drawable) and not v.drawn_by_group),
120
+ key= lambda uid : self.entities[uid].render_order
121
+ )
122
+
123
+ def debug_entity(self, uid: int):
124
+ entity = self.entities[uid]
125
+ def draw_rect(data):
126
+ if data is None:
127
+ return
128
+ if isinstance(data, pygame.FRect) or isinstance(data, pygame.Rect):
129
+ rect = data
130
+ color = entity.debug_color
131
+ else:
132
+ rect = data[0]
133
+ color = data[1]
134
+ if self.camera.intersects(rect):
135
+ pygame.draw.rect(self.camera.surface, color, self.camera.world_to_screen(rect), 1)
136
+
137
+ for data in entity.get_debug_outlines():
138
+ draw_rect(data)
@@ -8,9 +8,9 @@ def swap(lst, index1, index2):
8
8
 
9
9
  class SceneManager:
10
10
  def __init__(self) -> None:
11
- self.scenes: list[bf.Scene] = []
11
+ self.scenes: list[bf.BaseScene] = []
12
12
  self.shared_events = {pygame.WINDOWRESIZED}
13
- self.current_transitions: dict[str, bf.transition.Transition] = {}
13
+ self.current_transition : tuple[str,bf.transition.Transition,int] | None= None
14
14
 
15
15
  def init_scenes(self, *initial_scenes:bf.Scene):
16
16
  for index, s in enumerate(initial_scenes):
@@ -56,7 +56,7 @@ class SceneManager:
56
56
  print("\n" + "=" * 50)
57
57
  print(" DEBUGGING STATUS".center(50))
58
58
  print("=" * 50)
59
- print(f"[Debugging Mode] = {self.debug_mode}")
59
+ print(f"[Debugging Mode] = {bf.ResourceManager().get_sharedVar("debug_mode")}")
60
60
 
61
61
  # Print shared variables
62
62
  print("\n" + "=" * 50)
@@ -86,7 +86,7 @@ class SceneManager:
86
86
  if scene in self.scenes and not self.has_scene(scene.name):
87
87
  return
88
88
  scene.set_manager(self)
89
- scene.do_when_added()
89
+ scene.when_added()
90
90
  self.scenes.insert(0, scene)
91
91
 
92
92
  def remove_scene(self, name: str):
@@ -110,36 +110,34 @@ class SceneManager:
110
110
  def transition_to_scene(
111
111
  self,
112
112
  scene_name: str,
113
- transition: bf.transition.Transition = bf.transition.Fade(0.1),
113
+ transition: bf.transition.Transition = None,
114
114
  index: int = 0,
115
115
  ):
116
- target_scene = self.get_scene(scene_name)
117
- if not target_scene:
116
+ if transition is None:
117
+ transition = bf.transition.Fade(0.1)
118
+ if not (target_scene := self.get_scene(scene_name)):
118
119
  print(f"Scene '{scene_name}' does not exist")
119
120
  return
120
- if len(self.scenes) == 0 or index >= len(self.scenes) or index < 0:
121
- return
121
+ if not (source_scene := self.get_scene_at(index)):
122
+ print(f"No scene exists at index {index}.")
123
+ return
124
+
122
125
  source_surface = bf.const.SCREEN.copy()
123
126
  dest_surface = bf.const.SCREEN.copy()
124
127
 
125
- # self.draw(source_surface)
126
- target_scene.draw(dest_surface)
127
- target_scene.do_on_enter_early()
128
- self.get_scene_at(index).do_on_exit_early()
129
- self.current_transitions = {"scene_name": scene_name, "transition": transition}
130
- transition.set_start_callback(lambda: self._start_transition(target_scene))
131
- transition.set_end_callback(lambda: self._end_transition(scene_name, index))
128
+ target_scene.draw(dest_surface) # draw at least once to ensure smooth transition
129
+ target_scene.set_active(True)
130
+ target_scene.set_visible(True)
131
+
132
+ target_scene.do_on_enter_early()
133
+ source_scene.do_on_exit_early()
134
+
135
+ self.current_transition :tuple[str,bf.transition.Transition]=(scene_name,transition,index)
132
136
  transition.set_source(source_surface)
133
137
  transition.set_dest(dest_surface)
134
138
  transition.start()
135
139
 
136
- def _start_transition(self, target_scene: bf.Scene):
137
- target_scene.set_active(True)
138
- target_scene.set_visible(True)
139
140
 
140
- def _end_transition(self, scene_name, index):
141
- self.set_scene(scene_name, index, True)
142
- self.current_transitions.clear()
143
141
 
144
142
  def set_scene(self, scene_name, index=0, ignore_early: bool = False):
145
143
  target_scene = self.get_scene(scene_name)
@@ -171,9 +169,6 @@ class SceneManager:
171
169
 
172
170
  def process_event(self, event: pygame.Event):
173
171
 
174
- if self.current_transitions and event in bf.enums.playerInput:
175
- return
176
-
177
172
  if event.type in self.shared_events:
178
173
  [s.process_event(event) for s in self.scenes]
179
174
  else:
@@ -182,21 +177,21 @@ class SceneManager:
182
177
  def update(self, dt: float) -> None:
183
178
  for scene in self.active_scenes:
184
179
  scene.update(dt)
180
+ if self.current_transition and self.current_transition[1].is_over:
181
+ self.set_scene(self.current_transition[0],self.current_transition[2],True)
182
+ self.current_transition = None
185
183
  self.do_update(dt)
186
184
 
187
185
  def do_update(self, dt: float):
188
186
  pass
189
187
 
190
- def draw(self, surface) -> None:
188
+ def draw(self, surface:pygame.Surface) -> None:
191
189
  for scene in self.visible_scenes:
192
190
  scene.draw(surface)
193
- if self.current_transitions:
194
- self._draw_transition(surface)
195
-
196
- def _draw_transition(self, surface):
197
- self.current_transitions["transition"].set_source(surface)
198
- tmp = bf.const.SCREEN.copy()
199
- self.get_scene(self.current_transitions["scene_name"]).draw(tmp)
200
- self.current_transitions["transition"].set_dest(tmp)
201
- self.current_transitions["transition"].draw(surface)
202
- return
191
+ if self.current_transition is not None:
192
+ self.current_transition[1].set_source(surface)
193
+ tmp = surface.copy()
194
+ self.get_scene(self.current_transition[0]).draw(tmp)
195
+ self.current_transition[1].set_dest(tmp)
196
+ self.current_transition[1].draw(surface)
197
+
@@ -66,7 +66,7 @@ class ScrollingSprite(bf.Sprite):
66
66
  self.rect = self.surface.get_frect(topleft=self.rect.topleft)
67
67
  return self
68
68
 
69
- def _get_mosaic_rect_list(self) -> Iterator[pygame.Rect]:
69
+ def _get_mosaic_rect_list(self,camera:bf.Camera=None) -> Iterator[pygame.Rect]:
70
70
  # Use integer values for the starting points, converted from floating point scroll values
71
71
  start_x = int(self.scroll_value.x % self.original_width)
72
72
  start_y = int(self.scroll_value.y % self.original_height)
@@ -90,7 +90,12 @@ class ScrollingSprite(bf.Sprite):
90
90
  while x < end_x:
91
91
  y = y_position
92
92
  while y < end_y:
93
- yield pygame.Rect(x, y, self.original_width, self.original_height)
93
+ r = pygame.Rect(x, y, self.original_width, self.original_height)
94
+
95
+ if camera and camera.rect.colliderect((x+camera.rect.x,y+camera.rect.y,self.original_width,self.original_height)):
96
+ yield r
97
+ else:
98
+ yield r
94
99
  y += self.original_height
95
100
  x += self.original_width
96
101
  return self
@@ -104,7 +109,7 @@ class ScrollingSprite(bf.Sprite):
104
109
  return
105
110
  # self.surface.fill((0, 0, 0, 0))
106
111
  camera.surface.fblits(
107
- [(self.original_surface, r.move(self.rect.x-camera.rect.x,self.rect.y-camera.rect.y)) for r in self._get_mosaic_rect_list()]
112
+ [(self.original_surface, r.move(self.rect.x-camera.rect.x,self.rect.y-camera.rect.y)) for r in self._get_mosaic_rect_list(camera)]
108
113
  )
109
114
  # camera.surface.blit(self.surface, camera.world_to_screen(self.rect))
110
115
  return
batFramework/sprite.py CHANGED
@@ -6,8 +6,8 @@ from typing import Self
6
6
  class Sprite(bf.Drawable):
7
7
  def __init__(
8
8
  self,
9
- size: None | tuple[int, int] = None,
10
9
  path=None,
10
+ size: None | tuple[int, int] = None,
11
11
  convert_alpha: bool = True,
12
12
  ):
13
13
  self.original_surface: pygame.Surface = None
@@ -1,2 +1 @@
1
- from .character import *
2
- from .states import *
1
+ from .controller import *
@@ -0,0 +1,97 @@
1
+ import batFramework as bf
2
+ import pygame
3
+
4
+ class PlatformController(bf.DynamicEntity):
5
+ def __init__(self,*args,**kwargs):
6
+ super().__init__(*args,**kwargs)
7
+ self.control = bf.ActionContainer()
8
+ self.speed = 500
9
+ self.acceleration = 100
10
+ self.jump_force = -500
11
+ self.gravity = 1200
12
+ self.on_ground = False
13
+ self.friction = 0.7
14
+
15
+ def do_reset_actions(self):
16
+ self.control.reset()
17
+
18
+ def do_process_actions(self, event):
19
+ self.control.process_event(event)
20
+
21
+ def check_collision_y(self):
22
+ pass
23
+
24
+ def check_collision_x(self):
25
+ pass
26
+
27
+ def update(self, dt):
28
+ super().update(dt)
29
+ self.velocity.x *= self.friction
30
+ if abs(self.velocity.x) <= 0.01:
31
+ self.velocity.x = 0
32
+ if not self.on_ground:
33
+ self.velocity.y += self.gravity * dt
34
+ if self.control.is_active("left"):
35
+ self.velocity.x -= self.acceleration
36
+ if self.control.is_active("right"):
37
+ self.velocity.x += self.acceleration
38
+ if self.on_ground and self.control.is_active("jump") :
39
+ self.velocity.y = self.jump_force
40
+ self.on_ground = False
41
+
42
+ self.velocity.x = pygame.math.clamp(self.velocity.x,-self.speed,self.speed)
43
+ self.rect.x += self.velocity.x * dt
44
+ self.check_collision_x()
45
+ self.rect.y += self.velocity.y * dt
46
+ self.check_collision_y()
47
+
48
+
49
+
50
+ class TopDownController(bf.DynamicEntity):
51
+ def __init__(self,*args,**kwargs):
52
+ super().__init__(*args,**kwargs)
53
+ self.control = bf.ActionContainer()
54
+ self.input_velocity = pygame.Vector2()
55
+ self.speed = 500
56
+ self.acceleration = 100
57
+ self.friction = 0
58
+
59
+ def do_reset_actions(self):
60
+ self.control.reset()
61
+
62
+ def do_process_actions(self, event):
63
+ self.control.process_event(event)
64
+
65
+ def check_collision_y(self):
66
+ pass
67
+
68
+ def check_collision_x(self):
69
+ pass
70
+
71
+ def update(self, dt):
72
+ super().update(dt)
73
+ self.input_velocity.update(0,0)
74
+ self.velocity *= self.friction
75
+
76
+ if abs(self.velocity.x) <= 0.01:
77
+ self.velocity.x = 0
78
+ if self.control.is_active("left"):
79
+ self.input_velocity[0] = -self.acceleration
80
+ if self.control.is_active("right"):
81
+ self.input_velocity[0] = self.acceleration
82
+ if self.control.is_active("up"):
83
+ self.input_velocity[1] = -self.acceleration
84
+ if self.control.is_active("down"):
85
+ self.input_velocity[1] = self.acceleration
86
+
87
+ if self.input_velocity:
88
+ self.input_velocity.normalize_ip()
89
+
90
+ self.velocity += self.input_velocity * self.acceleration
91
+ self.velocity.clamp_magnitude_ip(self.speed)
92
+
93
+
94
+ self.rect.x += self.velocity.x * dt
95
+ self.check_collision_x()
96
+ self.rect.y += self.velocity.y * dt
97
+ self.check_collision_y()
@@ -1,12 +1,11 @@
1
1
  import batFramework as bf
2
2
  from typing import Callable, Union, Self,Any
3
3
 
4
-
5
4
  class Timer:
6
5
  _count: int = 0
7
6
  _available_ids: set[int] = set()
8
7
 
9
- def __init__(self, duration: Union[float, int], end_callback: Callable[[],Any], loop: bool = False, register: str = "global") -> None:
8
+ def __init__(self, duration: float, end_callback: Callable[[], Any], loop: int = 0, register: str = "global") -> None:
10
9
  if Timer._available_ids:
11
10
  self.uid = Timer._available_ids.pop()
12
11
  else:
@@ -14,85 +13,133 @@ class Timer:
14
13
  Timer._count += 1
15
14
 
16
15
  self.register = register
17
- self.duration: int | float = duration
16
+ self.duration: float = duration
18
17
  self.end_callback = end_callback
19
18
 
20
- self.elapsed_time: float = -1
19
+ self.elapsed_time: float = 0
21
20
  self.is_over: bool = False
22
- self.is_looping: bool = loop
21
+ self.loop: int = loop # Number of loops (-1 for infinite)
23
22
  self.is_paused: bool = False
24
23
  self.do_delete: bool = False
24
+ self.is_stopped: bool = True
25
25
 
26
- def __bool__(self)->bool:
27
- return self.elapsed_time==-1 or self.is_over
26
+ def __bool__(self) -> bool:
27
+ return self.elapsed_time != -1 and self.is_over
28
28
 
29
29
  def __str__(self) -> str:
30
- return f"Timer ({self.uid}) {self.elapsed_time}/{self.duration} | {'loop ' if self.is_looping else ''} {'(D) ' if self.do_delete else ''}"
30
+ loop_info = "infinite" if self.loop == -1 else f"{self.loop} loops left"
31
+ return f"Timer ({self.uid}) {self.elapsed_time}/{self.duration} | {loop_info} {'(D) ' if self.do_delete else ''}"
31
32
 
32
33
  def stop(self) -> Self:
33
- self.elapsed_time = -1
34
- self.is_over = False
34
+ """
35
+ Cancels all progression and stops the timer.
36
+ Does not mark it for deletion and does not call the end_callback.
37
+ Prevents automatic restart if looping.
38
+ """
39
+ self.is_stopped = True
35
40
  self.is_paused = False
41
+ self.is_over = False
42
+ self.elapsed_time = 0
36
43
  return self
37
44
 
38
45
  def start(self, force: bool = False) -> Self:
39
- if self.elapsed_time != -1 and not force:
46
+ """
47
+ Starts the timer only if not already started (unless force is used, which resets it).
48
+ """
49
+ if self.elapsed_time > 0 and not force:
40
50
  return self
41
- if not bf.TimeManager().add_timer(self,self.register):
51
+ if not bf.TimeManager().add_timer(self, self.register):
42
52
  return self
43
53
  self.elapsed_time = 0
44
54
  self.is_paused = False
45
55
  self.is_over = False
56
+ self.is_stopped = False
46
57
  return self
47
58
 
48
59
  def pause(self) -> Self:
60
+ """
61
+ Momentarily stops the timer until resume is called.
62
+ """
49
63
  self.is_paused = True
50
64
  return self
51
65
 
52
66
  def resume(self) -> Self:
67
+ """
68
+ Resumes from a paused state.
69
+ """
53
70
  self.is_paused = False
54
71
  return self
55
72
 
56
73
  def delete(self) -> Self:
74
+ """
75
+ Marks the timer for deletion.
76
+ """
57
77
  self.do_delete = True
58
78
  return self
59
79
 
60
80
  def has_started(self) -> bool:
61
- return self.elapsed_time != -1
81
+ """
82
+ Returns True if the timer has started.
83
+ """
84
+ return not self.is_stopped
62
85
 
63
86
  def get_progression(self) -> float:
64
- if self.elapsed_time < 0:
87
+ """
88
+ Returns the progression of the timer (0 to 1) as a float.
89
+ """
90
+ if self.is_stopped:
65
91
  return 0
66
92
  if self.elapsed_time >= self.duration:
67
93
  return 1
68
94
  return self.elapsed_time / self.duration
69
95
 
70
96
  def update(self, dt) -> None:
71
- if self.elapsed_time < 0 or self.is_paused or self.is_over:
97
+ if self.is_stopped or self.is_paused or self.is_over:
72
98
  return
73
99
  self.elapsed_time += dt
74
- # print("update :",self.elapsed_time,self.duration)
75
100
  if self.get_progression() == 1:
76
101
  self.end()
77
102
 
78
103
  def end(self):
104
+ """
105
+ Ends the timer progression (calls the end_callback function).
106
+ Is called automatically once the timer is over.
107
+ Will not mark the timer for deletion.
108
+ If it is looping, it will restart the timer **only if it wasn't stopped**.
109
+ """
110
+ self.is_over = True
79
111
  if self.end_callback:
80
112
  self.end_callback()
81
- self.elapsed_time = -1
82
- self.is_over = True
83
- if self.is_looping:
113
+
114
+ # Handle looping
115
+ if self.loop == -1: # Infinite looping
116
+ self.elapsed_time = 0
84
117
  self.start()
85
118
  return
119
+ elif self.loop > 0: # Decrease loop count and restart
120
+ self.loop -= 1
121
+ self.elapsed_time = 0
122
+ self.start()
123
+ return
124
+
125
+ # Stop the timer if no loops are left
126
+ self.is_stopped = True
86
127
 
87
128
  def should_delete(self) -> bool:
129
+ """
130
+ Method that returns if the timer is to be deleted.
131
+ Required for timer management.
132
+ """
88
133
  return self.is_over or self.do_delete
89
134
 
90
135
  def _release_id(self):
91
136
  Timer._available_ids.add(self.uid)
92
137
 
93
-
94
138
  class SceneTimer(Timer):
95
- def __init__(self, duration: float | int, end_callback, loop: bool = False, scene_name:str = "global") -> None:
139
+ """
140
+ A timer that is only updated while the given scene is active (being updated)
141
+ """
142
+ def __init__(self, duration: float | int, end_callback, loop: int = 0, scene_name:str = "global") -> None:
96
143
  super().__init__(duration, end_callback, loop, scene_name)
97
144
 
98
145
  class TimeManager(metaclass=bf.Singleton):
@@ -125,6 +172,13 @@ class TimeManager(metaclass=bf.Singleton):
125
172
  if name not in self.registers:
126
173
  self.registers[name] = TimeManager.TimerRegister(active)
127
174
 
175
+ def remove_register(self, name):
176
+ if name not in self.registers:
177
+ return
178
+
179
+ self.registers.pop(name)
180
+
181
+
128
182
  def add_timer(self, timer, register="global") -> bool:
129
183
  if register in self.registers:
130
184
  self.registers[register].add_timer(timer)
@@ -136,7 +190,7 @@ class TimeManager(metaclass=bf.Singleton):
136
190
  return [t for t in self.registers.values() if t.active]
137
191
 
138
192
  def update(self, dt):
139
- for register_name, register in self.registers.items():
193
+ for register in self.registers.values():
140
194
  if register.active:
141
195
  register.update(dt)
142
196