batframework 1.0.10__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.
- batFramework/__init__.py +84 -52
- batFramework/action.py +280 -252
- batFramework/actionContainer.py +105 -38
- batFramework/animatedSprite.py +81 -117
- batFramework/animation.py +91 -0
- batFramework/audioManager.py +156 -85
- batFramework/baseScene.py +249 -0
- batFramework/camera.py +245 -123
- batFramework/constants.py +57 -75
- batFramework/cutscene.py +239 -119
- batFramework/cutsceneManager.py +34 -0
- batFramework/drawable.py +107 -0
- batFramework/dynamicEntity.py +30 -23
- batFramework/easingController.py +58 -0
- batFramework/entity.py +130 -123
- batFramework/enums.py +171 -0
- batFramework/fontManager.py +65 -0
- batFramework/gui/__init__.py +28 -14
- batFramework/gui/animatedLabel.py +90 -0
- batFramework/gui/button.py +18 -84
- batFramework/gui/clickableWidget.py +244 -0
- batFramework/gui/collapseContainer.py +98 -0
- batFramework/gui/constraints/__init__.py +1 -0
- batFramework/gui/constraints/constraints.py +1066 -0
- batFramework/gui/container.py +220 -49
- batFramework/gui/debugger.py +140 -47
- batFramework/gui/draggableWidget.py +63 -0
- batFramework/gui/image.py +61 -23
- batFramework/gui/indicator.py +116 -40
- batFramework/gui/interactiveWidget.py +243 -22
- batFramework/gui/label.py +147 -110
- batFramework/gui/layout.py +442 -81
- batFramework/gui/meter.py +155 -0
- batFramework/gui/radioButton.py +43 -0
- batFramework/gui/root.py +228 -60
- batFramework/gui/scrollingContainer.py +282 -0
- batFramework/gui/selector.py +232 -0
- batFramework/gui/shape.py +286 -86
- batFramework/gui/slider.py +353 -0
- batFramework/gui/style.py +10 -0
- batFramework/gui/styleManager.py +49 -0
- batFramework/gui/syncedVar.py +43 -0
- batFramework/gui/textInput.py +331 -0
- batFramework/gui/textWidget.py +308 -0
- batFramework/gui/toggle.py +140 -62
- batFramework/gui/tooltip.py +35 -0
- batFramework/gui/widget.py +546 -307
- batFramework/manager.py +131 -50
- batFramework/particle.py +118 -0
- batFramework/propertyEaser.py +79 -0
- batFramework/renderGroup.py +34 -0
- batFramework/resourceManager.py +130 -0
- batFramework/scene.py +31 -226
- batFramework/sceneLayer.py +134 -0
- batFramework/sceneManager.py +200 -165
- batFramework/scrollingSprite.py +115 -0
- batFramework/sprite.py +46 -0
- batFramework/stateMachine.py +49 -51
- batFramework/templates/__init__.py +2 -0
- batFramework/templates/character.py +15 -0
- batFramework/templates/controller.py +158 -0
- batFramework/templates/stateMachine.py +39 -0
- batFramework/tileset.py +46 -0
- batFramework/timeManager.py +213 -0
- batFramework/transition.py +162 -157
- batFramework/triggerZone.py +22 -22
- batFramework/utils.py +306 -184
- {batframework-1.0.10.dist-info → batframework-2.0.0.dist-info}/LICENSE +1 -1
- {batframework-1.0.10.dist-info → batframework-2.0.0.dist-info}/METADATA +3 -4
- batframework-2.0.0.dist-info/RECORD +72 -0
- batFramework/cutsceneBlocks.py +0 -176
- batFramework/debugger.py +0 -48
- batFramework/easing.py +0 -71
- batFramework/gui/constraints.py +0 -204
- batFramework/gui/frame.py +0 -19
- batFramework/particles.py +0 -77
- batFramework/time.py +0 -75
- batFramework/transitionManager.py +0 -0
- batframework-1.0.10.dist-info/RECORD +0 -43
- {batframework-1.0.10.dist-info → batframework-2.0.0.dist-info}/WHEEL +0 -0
- {batframework-1.0.10.dist-info → batframework-2.0.0.dist-info}/top_level.txt +0 -0
batFramework/audioManager.py
CHANGED
@@ -1,85 +1,156 @@
|
|
1
|
-
import
|
2
|
-
import
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
self.
|
10
|
-
self.
|
11
|
-
|
12
|
-
self.
|
13
|
-
self.
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
self.
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
"sound"
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
return
|
85
|
-
|
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
|