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.
- batFramework/__init__.py +20 -11
- batFramework/action.py +1 -1
- batFramework/animatedSprite.py +47 -116
- batFramework/animation.py +30 -5
- batFramework/audioManager.py +8 -5
- batFramework/baseScene.py +240 -0
- batFramework/camera.py +4 -0
- batFramework/constants.py +6 -2
- batFramework/cutscene.py +221 -21
- batFramework/cutsceneManager.py +5 -2
- batFramework/drawable.py +7 -5
- batFramework/easingController.py +10 -11
- batFramework/entity.py +21 -2
- batFramework/enums.py +48 -33
- batFramework/gui/__init__.py +6 -3
- batFramework/gui/animatedLabel.py +10 -2
- batFramework/gui/button.py +4 -31
- batFramework/gui/clickableWidget.py +63 -50
- batFramework/gui/constraints/constraints.py +212 -136
- batFramework/gui/container.py +77 -58
- batFramework/gui/debugger.py +12 -17
- batFramework/gui/draggableWidget.py +21 -17
- batFramework/gui/image.py +3 -10
- batFramework/gui/indicator.py +56 -1
- batFramework/gui/interactiveWidget.py +127 -108
- batFramework/gui/label.py +73 -64
- batFramework/gui/layout.py +286 -445
- batFramework/gui/meter.py +42 -20
- batFramework/gui/radioButton.py +20 -69
- batFramework/gui/root.py +99 -29
- batFramework/gui/selector.py +250 -0
- batFramework/gui/shape.py +13 -5
- batFramework/gui/slider.py +262 -107
- batFramework/gui/syncedVar.py +49 -0
- batFramework/gui/textInput.py +46 -22
- batFramework/gui/toggle.py +70 -52
- batFramework/gui/tooltip.py +30 -0
- batFramework/gui/widget.py +222 -135
- batFramework/manager.py +7 -8
- batFramework/particle.py +4 -1
- batFramework/propertyEaser.py +79 -0
- batFramework/renderGroup.py +17 -50
- batFramework/resourceManager.py +43 -13
- batFramework/scene.py +15 -335
- batFramework/sceneLayer.py +138 -0
- batFramework/sceneManager.py +31 -36
- batFramework/scrollingSprite.py +8 -3
- batFramework/sprite.py +1 -1
- batFramework/templates/__init__.py +1 -2
- batFramework/templates/controller.py +97 -0
- batFramework/timeManager.py +76 -22
- batFramework/transition.py +37 -103
- batFramework/utils.py +125 -66
- {batframework-1.0.9a7.dist-info → batframework-1.0.9a9.dist-info}/METADATA +24 -3
- batframework-1.0.9a9.dist-info/RECORD +67 -0
- {batframework-1.0.9a7.dist-info → batframework-1.0.9a9.dist-info}/WHEEL +1 -1
- batFramework/character.py +0 -27
- batFramework/templates/character.py +0 -43
- batFramework/templates/states.py +0 -166
- batframework-1.0.9a7.dist-info/RECORD +0 -63
- /batframework-1.0.9a7.dist-info/LICENCE → /batframework-1.0.9a9.dist-info/LICENSE +0 -0
- {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)
|
batFramework/sceneManager.py
CHANGED
@@ -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.
|
11
|
+
self.scenes: list[bf.BaseScene] = []
|
12
12
|
self.shared_events = {pygame.WINDOWRESIZED}
|
13
|
-
self.
|
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] = {
|
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.
|
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 =
|
113
|
+
transition: bf.transition.Transition = None,
|
114
114
|
index: int = 0,
|
115
115
|
):
|
116
|
-
|
117
|
-
|
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
|
121
|
-
|
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
|
-
|
126
|
-
target_scene.
|
127
|
-
target_scene.
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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.
|
194
|
-
self.
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
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
|
+
|
batFramework/scrollingSprite.py
CHANGED
@@ -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
|
-
|
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
@@ -1,2 +1 @@
|
|
1
|
-
from .
|
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()
|
batFramework/timeManager.py
CHANGED
@@ -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:
|
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:
|
16
|
+
self.duration: float = duration
|
18
17
|
self.end_callback = end_callback
|
19
18
|
|
20
|
-
self.elapsed_time: float =
|
19
|
+
self.elapsed_time: float = 0
|
21
20
|
self.is_over: bool = False
|
22
|
-
self.
|
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
|
26
|
+
def __bool__(self) -> bool:
|
27
|
+
return self.elapsed_time != -1 and self.is_over
|
28
28
|
|
29
29
|
def __str__(self) -> str:
|
30
|
-
|
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
|
-
|
34
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
82
|
-
|
83
|
-
if self.
|
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
|
-
|
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
|
193
|
+
for register in self.registers.values():
|
140
194
|
if register.active:
|
141
195
|
register.update(dt)
|
142
196
|
|