batframework 1.1.0__py3-none-any.whl → 2.0.0__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 (81) hide show
  1. batFramework/__init__.py +84 -51
  2. batFramework/action.py +280 -252
  3. batFramework/actionContainer.py +105 -38
  4. batFramework/animatedSprite.py +81 -117
  5. batFramework/animation.py +91 -0
  6. batFramework/audioManager.py +156 -85
  7. batFramework/baseScene.py +249 -0
  8. batFramework/camera.py +245 -123
  9. batFramework/constants.py +57 -75
  10. batFramework/cutscene.py +239 -119
  11. batFramework/cutsceneManager.py +34 -0
  12. batFramework/drawable.py +107 -0
  13. batFramework/dynamicEntity.py +30 -23
  14. batFramework/easingController.py +58 -0
  15. batFramework/entity.py +130 -123
  16. batFramework/enums.py +171 -0
  17. batFramework/fontManager.py +65 -0
  18. batFramework/gui/__init__.py +28 -14
  19. batFramework/gui/animatedLabel.py +90 -0
  20. batFramework/gui/button.py +18 -84
  21. batFramework/gui/clickableWidget.py +244 -0
  22. batFramework/gui/collapseContainer.py +98 -0
  23. batFramework/gui/constraints/__init__.py +1 -0
  24. batFramework/gui/constraints/constraints.py +1066 -0
  25. batFramework/gui/container.py +220 -49
  26. batFramework/gui/debugger.py +140 -47
  27. batFramework/gui/draggableWidget.py +63 -0
  28. batFramework/gui/image.py +61 -23
  29. batFramework/gui/indicator.py +116 -40
  30. batFramework/gui/interactiveWidget.py +243 -22
  31. batFramework/gui/label.py +147 -110
  32. batFramework/gui/layout.py +442 -81
  33. batFramework/gui/meter.py +155 -0
  34. batFramework/gui/radioButton.py +43 -0
  35. batFramework/gui/root.py +228 -60
  36. batFramework/gui/scrollingContainer.py +282 -0
  37. batFramework/gui/selector.py +232 -0
  38. batFramework/gui/shape.py +286 -86
  39. batFramework/gui/slider.py +353 -0
  40. batFramework/gui/style.py +10 -0
  41. batFramework/gui/styleManager.py +49 -0
  42. batFramework/gui/syncedVar.py +43 -0
  43. batFramework/gui/textInput.py +331 -0
  44. batFramework/gui/textWidget.py +308 -0
  45. batFramework/gui/toggle.py +140 -62
  46. batFramework/gui/tooltip.py +35 -0
  47. batFramework/gui/widget.py +546 -307
  48. batFramework/manager.py +131 -50
  49. batFramework/particle.py +118 -0
  50. batFramework/propertyEaser.py +79 -0
  51. batFramework/renderGroup.py +34 -0
  52. batFramework/resourceManager.py +130 -0
  53. batFramework/scene.py +31 -226
  54. batFramework/sceneLayer.py +134 -0
  55. batFramework/sceneManager.py +200 -165
  56. batFramework/scrollingSprite.py +115 -0
  57. batFramework/sprite.py +46 -0
  58. batFramework/stateMachine.py +49 -51
  59. batFramework/templates/__init__.py +2 -0
  60. batFramework/templates/character.py +15 -0
  61. batFramework/templates/controller.py +158 -0
  62. batFramework/templates/stateMachine.py +39 -0
  63. batFramework/tileset.py +46 -0
  64. batFramework/timeManager.py +213 -0
  65. batFramework/transition.py +162 -157
  66. batFramework/triggerZone.py +22 -22
  67. batFramework/utils.py +306 -184
  68. {batframework-1.1.0.dist-info → batframework-2.0.0.dist-info}/LICENSE +1 -1
  69. {batframework-1.1.0.dist-info → batframework-2.0.0.dist-info}/METADATA +8 -4
  70. batframework-2.0.0.dist-info/RECORD +72 -0
  71. batFramework/cutsceneBlocks.py +0 -176
  72. batFramework/debugger.py +0 -48
  73. batFramework/easing.py +0 -71
  74. batFramework/gui/constraints.py +0 -204
  75. batFramework/gui/frame.py +0 -19
  76. batFramework/particles.py +0 -77
  77. batFramework/time.py +0 -75
  78. batFramework/transitionManager.py +0 -0
  79. batframework-1.1.0.dist-info/RECORD +0 -43
  80. {batframework-1.1.0.dist-info → batframework-2.0.0.dist-info}/WHEEL +0 -0
  81. {batframework-1.1.0.dist-info → batframework-2.0.0.dist-info}/top_level.txt +0 -0
@@ -1,85 +1,156 @@
1
- import batFramework as bf
2
- import pygame
3
-
4
- pygame.mixer.init()
5
-
6
-
7
- class AudioManager(metaclass=bf.Singleton):
8
- def __init__(self):
9
- self.sounds: dict[str : dict[str, pygame.mixer.Sound, bool]] = {}
10
- self.musics: dict[str:str] = {}
11
- self.current_music = None
12
- self.music_volume = 1
13
- self.sound_volume = 1
14
- pygame.mixer_music.set_endevent(bf.const.MUSIC_END_EVENT)
15
-
16
- def free_sounds(self, force=False):
17
- if force:
18
- self.sounds = {}
19
- return
20
- to_remove = []
21
- for name, data in self.sounds.items():
22
- if not data["persistent"]:
23
- to_remove.append(name)
24
-
25
- _ = [self.sounds.pop(i) for i in to_remove]
26
-
27
- def set_sound_volume(self, volume: float):
28
- self.sound_volume = volume
29
-
30
- def set_music_volume(self, volume: float):
31
- self.music_volume = volume
32
- pygame.mixer_music.set_volume(volume)
33
-
34
- def has_sound(self, name):
35
- return name in self.sounds
36
-
37
- def load_sound(self, name, path, persistent=False) -> pygame.mixer.Sound:
38
- if name in self.sounds:
39
- return self.sounds[name]["sound"]
40
- path = bf.utils.get_path(path)
41
- self.sounds[name] = {
42
- "path": path,
43
- "sound": pygame.mixer.Sound(path),
44
- "persistent": persistent,
45
- }
46
- return self.sounds[name]["sound"]
47
-
48
- def play_sound(self, name, volume=1):
49
- self.sounds[name]["sound"].set_volume(volume * self.sound_volume)
50
- self.sounds[name]["sound"].play()
51
-
52
- def stop_sound(self, name):
53
- if name in self.sounds:
54
- self.sounds[name]["sound"].stop()
55
-
56
- def load_music(self, name, path):
57
- self.musics[name] = bf.utils.get_path(path)
58
-
59
- def play_music(self, name, loop=0, fade=500):
60
- if name in self.musics:
61
- pygame.mixer_music.load(self.musics[name])
62
- pygame.mixer_music.play(loop, fade_ms=fade)
63
- self.current_music = name
64
- else:
65
- print(f"Music '{name}' not found in AudioManager.")
66
-
67
- def stop_music(self):
68
- if not self.current_music:
69
- return
70
- pygame.mixer_music.stop()
71
-
72
- def fadeout_music(self, fade_ms: int):
73
- if not self.current_music:
74
- return
75
- pygame.mixer_music.fadeout(fade_ms)
76
-
77
- def pause_music(self):
78
- if not self.current_music:
79
- return
80
- pygame.mixer_music.pause()
81
-
82
- def resume_music(self):
83
- if not self.current_music:
84
- return
85
- pygame.mixer_music.unpause()
1
+ import pygame
2
+ import batFramework as bf
3
+
4
+ class AudioManager(metaclass=bf.Singleton):
5
+ def __init__(self) -> None:
6
+ self._sounds: dict[str, dict] = {}
7
+ self._musics: dict[str, str] = {}
8
+ self._current_music: str | None = None
9
+ self._music_volume: float = 1.0
10
+ self._sound_volume: float = 1.0
11
+
12
+ self._channels: dict[str, pygame.mixer.Channel] = {}
13
+ self._channel_volumes: dict[str, float] = {}
14
+ self._use_custom_channels: bool = False
15
+
16
+ pygame.mixer.music.set_endevent(bf.const.MUSIC_END_EVENT)
17
+
18
+ # --- Channel management ---
19
+ def setup_channels(self, channels: dict[str, int]) -> None:
20
+ """
21
+ Setup channels by providing a dict of {channel_name: channel_index}.
22
+ Enables custom channel management.
23
+ """
24
+ pygame.mixer.set_num_channels(max(channels.values()) + 1)
25
+ self._channels = {
26
+ name: pygame.mixer.Channel(idx) for name, idx in channels.items()
27
+ }
28
+ self._channel_volumes = {name: 1.0 for name in channels.keys()}
29
+ self._use_custom_channels = True
30
+
31
+ def set_channel_volume(self, channel_name: str, volume: float) -> None:
32
+ if channel_name in self._channels:
33
+ clamped = max(0.0, min(volume, 1.0))
34
+ self._channel_volumes[channel_name] = clamped
35
+ self._channels[channel_name].set_volume(clamped)
36
+
37
+ def get_channel_volume(self, channel_name: str) -> float:
38
+ return self._channel_volumes.get(channel_name, 1.0)
39
+
40
+ # --- Sound management ---
41
+ def load_sound(self, name: str, path: str, persistent: bool = False) -> pygame.mixer.Sound:
42
+ if name in self._sounds:
43
+ return self._sounds[name]["sound"]
44
+ path = bf.ResourceManager().get_path(path)
45
+ sound = pygame.mixer.Sound(path)
46
+ self._sounds[name] = {
47
+ "sound": sound,
48
+ "path": path,
49
+ "persistent": persistent,
50
+ }
51
+ return sound
52
+
53
+ def load_sounds(self, sounds_data: list[tuple[str, str, bool]]) -> None:
54
+ for name, path, persistent in sounds_data:
55
+ self.load_sound(name, path, persistent)
56
+
57
+ def play_sound(self, name: str, volume: float = 1.0, channel_name: str | None = None) -> bool:
58
+ sound_data = self._sounds.get(name)
59
+ if not sound_data:
60
+ print(f"[AudioManager] Sound '{name}' not loaded.")
61
+ return False
62
+ sound = sound_data["sound"]
63
+ volume = max(0.0, min(volume, 1.0)) * self._sound_volume
64
+
65
+ if self._use_custom_channels and channel_name:
66
+ channel = self._channels.get(channel_name)
67
+ if not channel:
68
+ print(f"[AudioManager] Channel '{channel_name}' not found. Using default channel.")
69
+ sound.set_volume(volume)
70
+ sound.play()
71
+ return True
72
+ channel.set_volume(volume * self._channel_volumes.get(channel_name, 1.0))
73
+ channel.play(sound)
74
+ else:
75
+ # Default pygame behavior: auto assign a free channel
76
+ sound.set_volume(volume)
77
+ sound.play()
78
+ return True
79
+
80
+ def stop_sound(self, name: str) -> bool:
81
+ sound_data = self._sounds.get(name)
82
+ if not sound_data:
83
+ print(f"[AudioManager] Sound '{name}' not loaded.")
84
+ return False
85
+ sound_data["sound"].stop()
86
+ return True
87
+
88
+ def free_sounds(self, force: bool = False) -> None:
89
+ if force:
90
+ self._sounds.clear()
91
+ else:
92
+ self._sounds = {
93
+ name: data for name, data in self._sounds.items() if data["persistent"]
94
+ }
95
+
96
+ def set_sound_volume(self, volume: float) -> None:
97
+ self._sound_volume = max(0.0, min(volume, 1.0))
98
+
99
+ def get_sound_volume(self) -> float:
100
+ return self._sound_volume
101
+
102
+ # --- Music management ---
103
+ def load_music(self, name: str, path: str) -> None:
104
+ self._musics[name] = bf.ResourceManager().get_path(path)
105
+
106
+ def load_musics(self, musics_data: list[tuple[str, str]]) -> None:
107
+ for name, path in musics_data:
108
+ self.load_music(name, path)
109
+
110
+ def play_music(self, name: str, loops: int = 0, fade_ms: int = 500) -> bool:
111
+ path = self._musics.get(name)
112
+ if not path:
113
+ print(f"[AudioManager] Music '{name}' not loaded.")
114
+ return False
115
+ try:
116
+ pygame.mixer.music.load(path)
117
+ pygame.mixer.music.set_volume(self._music_volume)
118
+ pygame.mixer.music.play(loops=loops, fade_ms=fade_ms)
119
+ self._current_music = name
120
+ return True
121
+ except pygame.error as e:
122
+ print(f"[AudioManager] Failed to play music '{name}': {e}")
123
+ return False
124
+
125
+ def stop_music(self) -> None:
126
+ if self._current_music:
127
+ pygame.mixer.music.stop()
128
+ self._current_music = None
129
+
130
+ def fadeout_music(self, fade_ms: int) -> None:
131
+ if self._current_music:
132
+ pygame.mixer.music.fadeout(fade_ms)
133
+ self._current_music = None
134
+
135
+ def pause_music(self) -> None:
136
+ if self._current_music:
137
+ pygame.mixer.music.pause()
138
+
139
+ def resume_music(self) -> None:
140
+ if self._current_music:
141
+ pygame.mixer.music.unpause()
142
+
143
+ def free_music(self) -> None:
144
+ if self._current_music:
145
+ pygame.mixer.music.unload()
146
+ self._current_music = None
147
+
148
+ def set_music_volume(self, volume: float) -> None:
149
+ self._music_volume = max(0.0, min(volume, 1.0))
150
+ pygame.mixer.music.set_volume(self._music_volume)
151
+
152
+ def get_music_volume(self) -> float:
153
+ return self._music_volume
154
+
155
+ def get_current_music(self) -> str | None:
156
+ return self._current_music
@@ -0,0 +1,249 @@
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING, Any
3
+ if TYPE_CHECKING:
4
+ from .manager import Manager
5
+ from .sceneManager import SceneManager
6
+
7
+ import pygame
8
+ import itertools
9
+ import batFramework as bf
10
+ from .sceneLayer import SceneLayer
11
+
12
+ class BaseScene:
13
+ def __init__(self,name: str) -> None:
14
+ """
15
+ Base Scene object.
16
+ Empty scene with no layers or gui setup
17
+ Args:
18
+ name: Name of the scene.
19
+ """
20
+ bf.TimeManager().add_register(name,False)
21
+ self.scene_index = 0
22
+ self.name = name
23
+ self.manager: Manager | None = None
24
+ self.active = False
25
+ self.visible = False
26
+ self.clear_color = bf.color.BLACK
27
+ self.actions: bf.ActionContainer = bf.ActionContainer()
28
+ self.early_actions: bf.ActionContainer = bf.ActionContainer()
29
+ self.scene_layers : list[SceneLayer] = []
30
+
31
+ def set_clear_color(self,color):
32
+ """
33
+ Sets the clear color of the entire scene.
34
+ This color will fill the scene before all layers are drawn.
35
+ Results will not show if a layer has an opaque fill color on top.
36
+ Set to None to skip.
37
+ """
38
+ self.clear_color = color
39
+
40
+ def get_clear_color(self)->pygame.typing.ColorLike:
41
+ return self.clear_color
42
+
43
+ def add_layer(self,layer:SceneLayer,index:int=0):
44
+ layer.set_scene(self)
45
+ self.scene_layers.insert(index,layer)
46
+
47
+ def remove_layer(self,index=0):
48
+ self.scene_layers.pop(index)
49
+
50
+ def set_layer(self,layername,layer:SceneLayer):
51
+ for i,l in enumerate(self.scene_layers[::]):
52
+ if l.name == layername:
53
+ self.scene_layers[i] = layer
54
+ layer.set_scene(self)
55
+
56
+ def set_layer_index(self,layername:str,index:int=-1):
57
+ index = min(index,len(self.scene_layers)-1)
58
+ layer = self.get_layer(layername)
59
+ if layer is None : return
60
+ self.scene_layers.remove(layer)
61
+ self.scene_layers.insert(index,layer)
62
+
63
+ def get_layer(self,name:str)->bf.SceneLayer:
64
+ for s in self.scene_layers:
65
+ if s.name == name:
66
+ return s
67
+ return None
68
+
69
+ def add(self,layer:str,*entities):
70
+ l = self.get_layer(layer)
71
+ if l is None : return
72
+ l.add(*entities)
73
+
74
+ def remove(self,layer:str,*entities):
75
+ l = self.get_layer(layer)
76
+ if l is None : return
77
+ l.remove(*entities)
78
+
79
+ def __str__(self)->str:
80
+ return f"Scene({self.name})"
81
+
82
+ def set_scene_index(self, index: int):
83
+ """Set the scene index."""
84
+ self.scene_index = index
85
+
86
+ def get_scene_index(self) -> int:
87
+ """Get the scene index."""
88
+ return self.scene_index
89
+
90
+ def when_added(self):
91
+ for s in self.scene_layers:
92
+ s.flush_entity_changes()
93
+ self.do_when_added()
94
+
95
+ def do_when_added(self):
96
+ pass
97
+
98
+ def set_manager(self, manager_link: Manager):
99
+ """Set the manager link for the scene."""
100
+ self.manager = manager_link
101
+ self.manager.update_scene_states()
102
+
103
+ def set_visible(self, value: bool):
104
+ """Set the visibility of the scene."""
105
+ self.visible = value
106
+ if self.manager:
107
+ self.manager.update_scene_states()
108
+
109
+ def set_active(self, value):
110
+ """Set the activity of the scene."""
111
+ self.active = value
112
+ if self.manager:
113
+ self.manager.update_scene_states()
114
+
115
+ def is_active(self) -> bool:
116
+ """Check if the scene is active."""
117
+ return self.active
118
+
119
+ def is_visible(self) -> bool:
120
+ """Check if the scene is visible."""
121
+ return self.visible
122
+
123
+ def get_name(self) -> str:
124
+ """Get the name of the scene."""
125
+ return self.name
126
+
127
+ def get_by_tags(self, *tags):
128
+ """Get entities by their tags."""
129
+ return itertools.chain.from_iterable(l.get_by_tags(*tags) for l in self.scene_layers)
130
+
131
+ def get_by_uid(self, uid) -> bf.Entity | None:
132
+ """Get an entity by its unique identifier."""
133
+ for l in self.scene_layers:
134
+ r = l.get_by_uid(uid)
135
+ if r is not None:
136
+ return r
137
+ return None
138
+
139
+ def add_actions(self, *action):
140
+ """Add actions to the scene."""
141
+ self.actions.add_actions(*action)
142
+
143
+ def add_early_actions(self, *action):
144
+ """Add actions to the scene."""
145
+ self.early_actions.add_actions(*action)
146
+
147
+ def process_event(self, event: pygame.Event):
148
+ """
149
+ Propagates event while it is not consumed.
150
+ In order : do_early_handle_event
151
+ -> scene early_actions
152
+ -> propagate to all layers
153
+ -> handle_event
154
+ -> scene actions.
155
+ at each step, if the event is consumed the propagation stops
156
+ """
157
+ self.do_early_handle_event(event)
158
+ if event.consumed: return
159
+ self.early_actions.process_event(event)
160
+ if event.consumed: return
161
+
162
+ if self.manager.current_transition and event.type in bf.enums.playerInput:
163
+ return
164
+
165
+ for l in self.scene_layers:
166
+ l.process_event(event)
167
+ if event.consumed : return
168
+
169
+ self.handle_event(event)
170
+
171
+ if event.consumed:return
172
+ self.actions.process_event(event)
173
+
174
+ # called before process event
175
+ def do_early_handle_event(self, event: pygame.Event):
176
+ """Called early in event propagation"""
177
+ pass
178
+
179
+ def handle_event(self, event: pygame.Event):
180
+ """called inside process_event but before resetting the scene's action container and propagating event to scene layers"""
181
+ pass
182
+
183
+ def update(self, dt):
184
+ """Update the scene. Do NOT override"""
185
+
186
+ #update all scene layers
187
+ for l in self.scene_layers:
188
+ l.update(dt)
189
+ self.do_update(dt)
190
+ self.actions.reset()
191
+ self.early_actions.reset()
192
+
193
+
194
+ def do_update(self, dt):
195
+ """Specific update within the scene."""
196
+ pass
197
+
198
+
199
+ def draw(self, surface: pygame.Surface):
200
+ if self.clear_color is not None:
201
+ surface.fill(self.clear_color)
202
+ self.do_early_draw(surface)
203
+
204
+ # Draw all layers back to front
205
+ for i,l in enumerate(reversed(self.scene_layers)):
206
+ #blit all layers onto surface
207
+ l.draw(surface)
208
+ if i < len(self.scene_layers)-1:
209
+ self.do_between_layer_draw(surface,l)
210
+ self.do_final_draw(surface)
211
+
212
+
213
+ def do_early_draw(self, surface: pygame.Surface):
214
+ """Called before any layer draw"""
215
+ pass
216
+
217
+ def do_between_layer_draw(self, surface: pygame.Surface,layer:SceneLayer):
218
+ """Called after drawing the argument layer (except the last layer)"""
219
+ pass
220
+
221
+ def do_final_draw(self, surface: pygame.Surface):
222
+ "Called after all layers"
223
+ pass
224
+
225
+ def on_enter(self):
226
+ self.set_active(True)
227
+ self.set_visible(True)
228
+ bf.TimeManager().activate_register(self.name)
229
+ self.do_on_enter()
230
+
231
+ def on_exit(self):
232
+ self.set_active(False)
233
+ self.set_visible(False)
234
+ self.actions.hard_reset()
235
+ self.early_actions.hard_reset()
236
+ bf.TimeManager().deactivate_register(self.name)
237
+ self.do_on_exit()
238
+
239
+ def do_on_enter(self) -> None:
240
+ pass
241
+
242
+ def do_on_exit(self) -> None:
243
+ pass
244
+
245
+ def do_on_enter_early(self) -> None:
246
+ pass
247
+
248
+ def do_on_exit_early(self) -> None:
249
+ pass