mini-arcade-core 0.9.9__py3-none-any.whl → 0.10.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.
- mini_arcade_core/__init__.py +24 -43
- mini_arcade_core/bus.py +57 -0
- mini_arcade_core/commands.py +84 -0
- mini_arcade_core/entity.py +3 -2
- mini_arcade_core/game.py +1 -1
- mini_arcade_core/managers/__init__.py +10 -2
- mini_arcade_core/managers/base.py +41 -0
- mini_arcade_core/{cheats.py → managers/cheats.py} +131 -11
- mini_arcade_core/managers/inputs.py +282 -0
- mini_arcade_core/managers/overlays.py +53 -0
- mini_arcade_core/managers/system.py +26 -0
- mini_arcade_core/scenes/__init__.py +12 -2
- mini_arcade_core/scenes/model.py +34 -0
- mini_arcade_core/scenes/runtime.py +29 -0
- mini_arcade_core/scenes/scene.py +35 -19
- mini_arcade_core/scenes/system.py +69 -0
- mini_arcade_core/spaces/__init__.py +12 -0
- mini_arcade_core/{two_d → spaces/d2}/kinematics2d.py +3 -0
- mini_arcade_core/ui/__init__.py +13 -1
- mini_arcade_core/ui/menu.py +232 -54
- mini_arcade_core/ui/overlays.py +41 -0
- {mini_arcade_core-0.9.9.dist-info → mini_arcade_core-0.10.0.dist-info}/METADATA +1 -1
- mini_arcade_core-0.10.0.dist-info/RECORD +40 -0
- mini_arcade_core/managers/overlay_manager.py +0 -33
- mini_arcade_core-0.9.9.dist-info/RECORD +0 -31
- /mini_arcade_core/managers/{entity_manager.py → entities.py} +0 -0
- /mini_arcade_core/{two_d → spaces/d2}/__init__.py +0 -0
- /mini_arcade_core/{two_d → spaces/d2}/boundaries2d.py +0 -0
- /mini_arcade_core/{two_d → spaces/d2}/collision2d.py +0 -0
- /mini_arcade_core/{two_d → spaces/d2}/geometry2d.py +0 -0
- /mini_arcade_core/{two_d → spaces/d2}/physics2d.py +0 -0
- {mini_arcade_core-0.9.9.dist-info → mini_arcade_core-0.10.0.dist-info}/WHEEL +0 -0
- {mini_arcade_core-0.9.9.dist-info → mini_arcade_core-0.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Input manager for handling input bindings and commands.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import TYPE_CHECKING, Callable, Dict, Optional
|
|
10
|
+
|
|
11
|
+
from mini_arcade_core.backend import Event, EventType
|
|
12
|
+
from mini_arcade_core.commands import BaseCommand, BaseSceneCommand
|
|
13
|
+
from mini_arcade_core.keymaps import Key
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from mini_arcade_core.scenes.scene import Scene
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
Predicate = Callable[["Event"], bool]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class InputBinding:
|
|
26
|
+
"""
|
|
27
|
+
Defines an input binding.
|
|
28
|
+
|
|
29
|
+
:ivar action (str): The action name.
|
|
30
|
+
:ivar command (BaseCommand): The command to execute.
|
|
31
|
+
:ivar predicate (Predicate): Predicate to match events.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
action: str
|
|
35
|
+
command: BaseCommand
|
|
36
|
+
predicate: Predicate # decides whether this binding matches an event
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class InputManager:
|
|
40
|
+
"""
|
|
41
|
+
Manager for handling input bindings and commands.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self):
|
|
45
|
+
# event_type -> key -> action -> command
|
|
46
|
+
self._bindings: Dict[EventType, Dict[Key, Dict[str, BaseCommand]]] = {}
|
|
47
|
+
|
|
48
|
+
# Justification: The method needs multiple optional parameters for flexibility.
|
|
49
|
+
# pylint: disable=too-many-arguments
|
|
50
|
+
def bind(
|
|
51
|
+
self,
|
|
52
|
+
event_type: EventType,
|
|
53
|
+
action: str,
|
|
54
|
+
command: BaseCommand,
|
|
55
|
+
*,
|
|
56
|
+
key: Optional[Key] = None,
|
|
57
|
+
button: Optional[int] = None,
|
|
58
|
+
predicate: Optional[Predicate] = None,
|
|
59
|
+
):
|
|
60
|
+
"""
|
|
61
|
+
Generic binding.
|
|
62
|
+
|
|
63
|
+
You can filter by:
|
|
64
|
+
- key: for KEYDOWN/KEYUP
|
|
65
|
+
- button: for MOUSEBUTTONDOWN/MOUSEBUTTONUP (if your Event exposes it)
|
|
66
|
+
- predicate: custom matcher (for anything)
|
|
67
|
+
|
|
68
|
+
:param event_type: The type of event to bind to.
|
|
69
|
+
:type event_type: EventType
|
|
70
|
+
|
|
71
|
+
:param action: The action name for the binding.
|
|
72
|
+
:type action: str
|
|
73
|
+
|
|
74
|
+
:param command: The command to execute when the binding is triggered.
|
|
75
|
+
:type command: BaseCommand
|
|
76
|
+
|
|
77
|
+
:param key: Optional key to filter KEYDOWN/KEYUP events.
|
|
78
|
+
:type key: Key | None
|
|
79
|
+
|
|
80
|
+
:param button: Optional button to filter MOUSEBUTTONDOWN/MOUSEBUTTONUP events.
|
|
81
|
+
:type button: int | None
|
|
82
|
+
|
|
83
|
+
:param predicate: Optional custom predicate to match events.
|
|
84
|
+
:type predicate: Predicate | None
|
|
85
|
+
"""
|
|
86
|
+
logger.debug(
|
|
87
|
+
f"Binding {action} to {event_type} with key={key}, button={button}"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def default_predicate(ev: Event) -> bool:
|
|
91
|
+
if key is not None and getattr(ev, "key", None) != key:
|
|
92
|
+
return False
|
|
93
|
+
if button is not None and getattr(ev, "button", None) != button:
|
|
94
|
+
return False
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
pred = predicate or default_predicate
|
|
98
|
+
self._bindings.setdefault(event_type, []).append(
|
|
99
|
+
InputBinding(action=action, command=command, predicate=pred)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# pylint: enable=too-many-arguments
|
|
103
|
+
|
|
104
|
+
def unbind(self, event_type: EventType, action: str):
|
|
105
|
+
"""
|
|
106
|
+
Remove bindings by action for an event type.
|
|
107
|
+
|
|
108
|
+
:param event_type: The type of event to unbind from.
|
|
109
|
+
:type event_type: EventType
|
|
110
|
+
|
|
111
|
+
:param action: The action name of the binding to remove.
|
|
112
|
+
:type action: str
|
|
113
|
+
"""
|
|
114
|
+
lst = self._bindings.get(event_type, [])
|
|
115
|
+
self._bindings[event_type] = [b for b in lst if b.action != action]
|
|
116
|
+
|
|
117
|
+
def clear(self):
|
|
118
|
+
"""Clear all input bindings."""
|
|
119
|
+
self._bindings.clear()
|
|
120
|
+
|
|
121
|
+
def handle_event(self, event: Event, scene: Scene):
|
|
122
|
+
"""
|
|
123
|
+
Handle an incoming event, executing any matching commands.
|
|
124
|
+
|
|
125
|
+
:param event: The event to handle.
|
|
126
|
+
:type event: Event
|
|
127
|
+
|
|
128
|
+
:param scene: The current scene context.
|
|
129
|
+
:type scene: Scene
|
|
130
|
+
"""
|
|
131
|
+
et = event.type
|
|
132
|
+
|
|
133
|
+
for binding in self._bindings.get(et, []):
|
|
134
|
+
if binding.predicate(event):
|
|
135
|
+
to_inject = (
|
|
136
|
+
scene.model
|
|
137
|
+
if isinstance(binding.command, BaseSceneCommand)
|
|
138
|
+
else scene.game
|
|
139
|
+
)
|
|
140
|
+
binding.command.execute(to_inject)
|
|
141
|
+
|
|
142
|
+
# --- Convenience API ------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
def on_quit(self, command: BaseCommand, action: str = "quit"):
|
|
145
|
+
"""
|
|
146
|
+
Bind a command to the QUIT event.
|
|
147
|
+
|
|
148
|
+
:param command: The command to execute on quit.
|
|
149
|
+
:type command: BaseCommand
|
|
150
|
+
|
|
151
|
+
:param action: The action name for the binding.
|
|
152
|
+
:type action: str
|
|
153
|
+
"""
|
|
154
|
+
self.bind(EventType.QUIT, action=action, command=command)
|
|
155
|
+
|
|
156
|
+
def on_key_down(self, key: Key, command: BaseCommand, action: str):
|
|
157
|
+
"""
|
|
158
|
+
Bind a command to a key down event.
|
|
159
|
+
|
|
160
|
+
:param key: The key to bind to.
|
|
161
|
+
:type key: Key
|
|
162
|
+
|
|
163
|
+
:param command: The command to execute on key down.
|
|
164
|
+
:type command: BaseCommand
|
|
165
|
+
|
|
166
|
+
:param action: The action name for the binding.
|
|
167
|
+
:type action: str
|
|
168
|
+
"""
|
|
169
|
+
self.bind(EventType.KEYDOWN, key=key, action=action, command=command)
|
|
170
|
+
|
|
171
|
+
def on_key_up(self, key: Key, command: BaseCommand, action: str):
|
|
172
|
+
"""
|
|
173
|
+
Bind a command to a key up event.
|
|
174
|
+
|
|
175
|
+
:param key: The key to bind to.
|
|
176
|
+
:type key: Key
|
|
177
|
+
|
|
178
|
+
:param command: The command to execute on key up.
|
|
179
|
+
:type command: BaseCommand
|
|
180
|
+
|
|
181
|
+
:param action: The action name for the binding.
|
|
182
|
+
:type action: str
|
|
183
|
+
"""
|
|
184
|
+
self.bind(EventType.KEYUP, key=key, action=action, command=command)
|
|
185
|
+
|
|
186
|
+
def on_mouse_button_down(
|
|
187
|
+
self, button: int, command: BaseCommand, action: str
|
|
188
|
+
):
|
|
189
|
+
"""
|
|
190
|
+
Bind a command to a mouse button down event.
|
|
191
|
+
|
|
192
|
+
:param button: The mouse button to bind to.
|
|
193
|
+
:type button: int
|
|
194
|
+
|
|
195
|
+
:param command: The command to execute on mouse button down.
|
|
196
|
+
:type command: BaseCommand
|
|
197
|
+
|
|
198
|
+
:param action: The action name for the binding.
|
|
199
|
+
:type action: str
|
|
200
|
+
"""
|
|
201
|
+
self.bind(
|
|
202
|
+
EventType.MOUSEBUTTONDOWN,
|
|
203
|
+
button=button,
|
|
204
|
+
action=action,
|
|
205
|
+
command=command,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
def on_mouse_button_up(
|
|
209
|
+
self, button: int, command: BaseCommand, action: str
|
|
210
|
+
):
|
|
211
|
+
"""
|
|
212
|
+
Bind a command to a mouse button up event.
|
|
213
|
+
|
|
214
|
+
:param button: The mouse button to bind to.
|
|
215
|
+
:type button: int
|
|
216
|
+
|
|
217
|
+
:param command: The command to execute on mouse button up.
|
|
218
|
+
:type command: BaseCommand
|
|
219
|
+
|
|
220
|
+
:param action: The action name for the binding.
|
|
221
|
+
:type action: str
|
|
222
|
+
"""
|
|
223
|
+
self.bind(
|
|
224
|
+
EventType.MOUSEBUTTONUP,
|
|
225
|
+
button=button,
|
|
226
|
+
action=action,
|
|
227
|
+
command=command,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
def on_mouse_motion(
|
|
231
|
+
self, command: BaseCommand, action: str = "mouse_motion"
|
|
232
|
+
):
|
|
233
|
+
"""
|
|
234
|
+
Bind a command to mouse motion events.
|
|
235
|
+
|
|
236
|
+
:param command: The command to execute on mouse motion.
|
|
237
|
+
:type command: BaseCommand
|
|
238
|
+
|
|
239
|
+
:param action: The action name for the binding.
|
|
240
|
+
:type action: str
|
|
241
|
+
"""
|
|
242
|
+
self.bind(EventType.MOUSEMOTION, action=action, command=command)
|
|
243
|
+
|
|
244
|
+
def on_mouse_wheel(
|
|
245
|
+
self, command: BaseCommand, action: str = "mouse_wheel"
|
|
246
|
+
):
|
|
247
|
+
"""
|
|
248
|
+
Bind a command to mouse wheel events.
|
|
249
|
+
|
|
250
|
+
:param command: The command to execute on mouse wheel.
|
|
251
|
+
:type command: BaseCommand
|
|
252
|
+
|
|
253
|
+
:param action: The action name for the binding.
|
|
254
|
+
:type action: str
|
|
255
|
+
"""
|
|
256
|
+
self.bind(EventType.MOUSEWHEEL, action=action, command=command)
|
|
257
|
+
|
|
258
|
+
def on_window_resized(
|
|
259
|
+
self, command: BaseCommand, action: str = "window_resized"
|
|
260
|
+
):
|
|
261
|
+
"""
|
|
262
|
+
Bind a command to window resized events.
|
|
263
|
+
|
|
264
|
+
:param command: The command to execute on window resize.
|
|
265
|
+
:type command: BaseCommand
|
|
266
|
+
|
|
267
|
+
:param action: The action name for the binding.
|
|
268
|
+
:type action: str
|
|
269
|
+
"""
|
|
270
|
+
self.bind(EventType.WINDOWRESIZED, action=action, command=command)
|
|
271
|
+
|
|
272
|
+
def on_text_input(self, command: BaseCommand, action: str = "text_input"):
|
|
273
|
+
"""
|
|
274
|
+
Bind a command to text input events.
|
|
275
|
+
|
|
276
|
+
:param command: The command to execute on text input.
|
|
277
|
+
:type command: BaseCommand
|
|
278
|
+
|
|
279
|
+
:param action: The action name for the binding.
|
|
280
|
+
:type action: str
|
|
281
|
+
"""
|
|
282
|
+
self.bind(EventType.TEXTINPUT, action=action, command=command)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Overlay manager for handling a collection of overlays.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Union
|
|
9
|
+
|
|
10
|
+
from mini_arcade_core.backend import Backend
|
|
11
|
+
from mini_arcade_core.managers.base import ListManager, Overlay, OverlayFunc
|
|
12
|
+
|
|
13
|
+
OverlayType = Union[Overlay, OverlayFunc]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class OverlayManager(ListManager[OverlayType]):
|
|
18
|
+
"""
|
|
19
|
+
Manages a collection of overlays within a scene.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def update(self, dt: float):
|
|
23
|
+
"""
|
|
24
|
+
Update all managed overlays.
|
|
25
|
+
|
|
26
|
+
:param dt: Time delta in seconds.
|
|
27
|
+
:type dt: float
|
|
28
|
+
"""
|
|
29
|
+
for ov in list(self.items):
|
|
30
|
+
# class overlays only
|
|
31
|
+
if hasattr(ov, "update") and hasattr(ov, "draw"):
|
|
32
|
+
if getattr(ov, "enabled", True):
|
|
33
|
+
ov.update(dt)
|
|
34
|
+
|
|
35
|
+
def draw(self, surface: "Backend"):
|
|
36
|
+
"""
|
|
37
|
+
Call all overlays. Scenes should call this at the end of draw().
|
|
38
|
+
|
|
39
|
+
:param surface: The backend surface to draw on.
|
|
40
|
+
:type surface: Backend
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def prio(o: OverlayType) -> int:
|
|
44
|
+
"""Priority for sorting overlays."""
|
|
45
|
+
return getattr(o, "priority", 0)
|
|
46
|
+
|
|
47
|
+
for ov in sorted(list(self.items), key=prio):
|
|
48
|
+
if hasattr(ov, "draw"):
|
|
49
|
+
if getattr(ov, "enabled", True):
|
|
50
|
+
ov.draw(surface)
|
|
51
|
+
else:
|
|
52
|
+
# function overlay
|
|
53
|
+
ov(surface)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manager for scene systems.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from mini_arcade_core.managers.base import ListManager
|
|
10
|
+
from mini_arcade_core.scenes.system import BaseSceneSystem
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class SystemManager(ListManager[BaseSceneSystem]):
|
|
15
|
+
"""
|
|
16
|
+
Manager for scene systems.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def sorted(self) -> list[BaseSceneSystem]:
|
|
20
|
+
"""
|
|
21
|
+
Get systems sorted by priority.
|
|
22
|
+
|
|
23
|
+
:return: List of systems sorted by priority.
|
|
24
|
+
:rtype: list[BaseSceneSystem]
|
|
25
|
+
"""
|
|
26
|
+
return sorted(self.items, key=lambda s: getattr(s, "priority", 0))
|
|
@@ -6,7 +6,17 @@ Provides the base Scene class and related functionality.
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
8
|
from .autoreg import register_scene
|
|
9
|
+
from .model import SceneModel
|
|
9
10
|
from .registry import SceneRegistry
|
|
10
|
-
from .
|
|
11
|
+
from .runtime import SceneRuntime
|
|
12
|
+
from .scene import Scene
|
|
13
|
+
from .system import BaseSceneSystem
|
|
11
14
|
|
|
12
|
-
__all__ = [
|
|
15
|
+
__all__ = [
|
|
16
|
+
"Scene",
|
|
17
|
+
"register_scene",
|
|
18
|
+
"SceneRegistry",
|
|
19
|
+
"SceneRuntime",
|
|
20
|
+
"SceneModel",
|
|
21
|
+
"BaseSceneSystem",
|
|
22
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scene base model module.
|
|
3
|
+
Provides the Scene class and related services.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, fields
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class SceneModel:
|
|
14
|
+
"""
|
|
15
|
+
Basic data model for a scene.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def _serialize_dataclass(obj) -> dict[str, Any]:
|
|
20
|
+
out = {}
|
|
21
|
+
for f in fields(obj):
|
|
22
|
+
if f.metadata.get("serialize", True) is False:
|
|
23
|
+
continue
|
|
24
|
+
out[f.name] = getattr(obj, f.name)
|
|
25
|
+
return out
|
|
26
|
+
|
|
27
|
+
def to_dict(self) -> dict:
|
|
28
|
+
"""
|
|
29
|
+
Convert the SceneModel to a dictionary.
|
|
30
|
+
|
|
31
|
+
:return: Dictionary representation of the SceneModel.
|
|
32
|
+
:rtype: dict
|
|
33
|
+
"""
|
|
34
|
+
return self._serialize_dataclass(self)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Container for scene services like entity and overlay managers.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
|
|
9
|
+
from mini_arcade_core.managers.entities import EntityManager
|
|
10
|
+
from mini_arcade_core.managers.inputs import InputManager
|
|
11
|
+
from mini_arcade_core.managers.overlays import OverlayManager
|
|
12
|
+
from mini_arcade_core.managers.system import SystemManager
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class SceneRuntime:
|
|
17
|
+
"""
|
|
18
|
+
Container for scene services like entity and overlay managers.
|
|
19
|
+
|
|
20
|
+
:ivar input (InputManager): InputManager for handling input bindings and commands.
|
|
21
|
+
:ivar entities (EntityManager): EntityManager for managing scene entities.
|
|
22
|
+
:ivar overlays (OverlayManager): OverlayManager for managing scene overlays.
|
|
23
|
+
:ivar systems (SystemManager): SystemManager for managing scene systems.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
input: InputManager = field(default_factory=InputManager)
|
|
27
|
+
entities: EntityManager = field(default_factory=EntityManager)
|
|
28
|
+
overlays: OverlayManager = field(default_factory=OverlayManager)
|
|
29
|
+
systems: SystemManager = field(default_factory=SystemManager)
|
mini_arcade_core/scenes/scene.py
CHANGED
|
@@ -5,39 +5,29 @@ Base class for game scenes (states/screens).
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
|
-
from dataclasses import dataclass, field
|
|
9
8
|
from typing import TYPE_CHECKING, List, Optional
|
|
10
9
|
|
|
11
10
|
from mini_arcade_core.backend import Backend, Event
|
|
12
11
|
from mini_arcade_core.entity import Entity
|
|
13
|
-
from mini_arcade_core.
|
|
14
|
-
from mini_arcade_core.
|
|
12
|
+
from mini_arcade_core.scenes.model import SceneModel
|
|
13
|
+
from mini_arcade_core.spaces.d2 import Size2D
|
|
14
|
+
|
|
15
|
+
from .runtime import SceneRuntime
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
17
18
|
from mini_arcade_core.game import Game
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
@dataclass
|
|
21
|
-
class SceneServices:
|
|
22
|
-
"""
|
|
23
|
-
Container for scene services like entity and overlay managers.
|
|
24
|
-
|
|
25
|
-
:ivar entities: EntityManager for managing scene entities.
|
|
26
|
-
:ivar overlays: OverlayManager for managing scene overlays.
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
entities: EntityManager = field(default_factory=EntityManager)
|
|
30
|
-
overlays: OverlayManager = field(default_factory=OverlayManager)
|
|
31
|
-
|
|
32
|
-
|
|
33
21
|
class Scene(ABC):
|
|
34
22
|
"""Base class for game scenes (states/screens)."""
|
|
35
23
|
|
|
24
|
+
model: SceneModel
|
|
25
|
+
|
|
36
26
|
def __init__(
|
|
37
27
|
self,
|
|
38
28
|
game: Game,
|
|
39
29
|
*,
|
|
40
|
-
services: Optional[
|
|
30
|
+
services: Optional[SceneRuntime] = None,
|
|
41
31
|
):
|
|
42
32
|
"""
|
|
43
33
|
:param game: Reference to the main Game object.
|
|
@@ -47,8 +37,8 @@ class Scene(ABC):
|
|
|
47
37
|
self.entities: List[Entity] = []
|
|
48
38
|
self.size: Size2D = Size2D(game.config.width, game.config.height)
|
|
49
39
|
|
|
50
|
-
self.services:
|
|
51
|
-
services if services is not None else
|
|
40
|
+
self.services: SceneRuntime = (
|
|
41
|
+
services if services is not None else SceneRuntime()
|
|
52
42
|
)
|
|
53
43
|
|
|
54
44
|
@abstractmethod
|
|
@@ -86,6 +76,32 @@ class Scene(ABC):
|
|
|
86
76
|
:type surface: Backend
|
|
87
77
|
"""
|
|
88
78
|
|
|
79
|
+
def _systems_on_enter(self):
|
|
80
|
+
for sys in self.services.systems.sorted():
|
|
81
|
+
if getattr(sys, "enabled", True):
|
|
82
|
+
sys.on_enter()
|
|
83
|
+
|
|
84
|
+
def _systems_on_exit(self):
|
|
85
|
+
for sys in self.services.systems.sorted():
|
|
86
|
+
if getattr(sys, "enabled", True):
|
|
87
|
+
sys.on_exit()
|
|
88
|
+
|
|
89
|
+
def _systems_handle_event(self, event: Event) -> bool:
|
|
90
|
+
for sys in self.services.systems.sorted():
|
|
91
|
+
if getattr(sys, "enabled", True) and sys.handle_event(event):
|
|
92
|
+
return True
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
def _systems_update(self, dt: float):
|
|
96
|
+
for sys in self.services.systems.sorted():
|
|
97
|
+
if getattr(sys, "enabled", True):
|
|
98
|
+
sys.update(dt)
|
|
99
|
+
|
|
100
|
+
def _systems_draw(self, surface: Backend):
|
|
101
|
+
for sys in self.services.systems.sorted():
|
|
102
|
+
if getattr(sys, "enabled", True):
|
|
103
|
+
sys.draw(surface)
|
|
104
|
+
|
|
89
105
|
def on_pause(self):
|
|
90
106
|
"""Called when the game is paused."""
|
|
91
107
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Protocol for scene systems.
|
|
3
|
+
A scene system is a modular component that can hook into the scene lifecycle
|
|
4
|
+
methods (on_enter, on_exit, handle_event, update, draw) to provide additional
|
|
5
|
+
functionality.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
from mini_arcade_core.backend import Backend, Event
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from mini_arcade_core.scenes.scene import Scene
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class BaseSceneSystem:
|
|
21
|
+
"""
|
|
22
|
+
Protocol for scene systems.
|
|
23
|
+
|
|
24
|
+
:ivar enabled (bool): Whether the system is enabled.
|
|
25
|
+
:ivar priority (int): Priority of the system (lower runs first).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
enabled: bool = True
|
|
29
|
+
priority: int = 0 # lower runs first
|
|
30
|
+
|
|
31
|
+
def __init__(self, scene: "Scene"):
|
|
32
|
+
self.scene = scene
|
|
33
|
+
|
|
34
|
+
def on_enter(self):
|
|
35
|
+
"""
|
|
36
|
+
Called when the scene is entered.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def on_exit(self):
|
|
40
|
+
"""
|
|
41
|
+
Called when the scene is exited.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def handle_event(self, event: "Event") -> bool:
|
|
45
|
+
"""
|
|
46
|
+
Handle an event.
|
|
47
|
+
|
|
48
|
+
:param event: The event to handle.
|
|
49
|
+
:type event: Event
|
|
50
|
+
|
|
51
|
+
:return: True if the event was handled, False otherwise.
|
|
52
|
+
:rtype: bool
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def update(self, dt: float):
|
|
56
|
+
"""
|
|
57
|
+
Update the system.
|
|
58
|
+
|
|
59
|
+
:param dt: Delta time since last update.
|
|
60
|
+
:type dt: float
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def draw(self, surface: "Backend"):
|
|
64
|
+
"""
|
|
65
|
+
Draw the system.
|
|
66
|
+
|
|
67
|
+
:param surface: The backend surface to draw on.
|
|
68
|
+
:type surface: Backend
|
|
69
|
+
"""
|
|
@@ -27,6 +27,7 @@ class KinematicData:
|
|
|
27
27
|
position: Position2D
|
|
28
28
|
size: Size2D
|
|
29
29
|
velocity: Velocity2D
|
|
30
|
+
time_scale: float = 1.0
|
|
30
31
|
color: Optional[Color] = None # future use
|
|
31
32
|
|
|
32
33
|
# Justification: Convenience factory with many params.
|
|
@@ -40,6 +41,7 @@ class KinematicData:
|
|
|
40
41
|
height: int,
|
|
41
42
|
vx: float = 0.0,
|
|
42
43
|
vy: float = 0.0,
|
|
44
|
+
time_scale: float = 1.0,
|
|
43
45
|
color: Optional[Color] = None,
|
|
44
46
|
) -> "KinematicData":
|
|
45
47
|
"""
|
|
@@ -73,6 +75,7 @@ class KinematicData:
|
|
|
73
75
|
position=Position2D(float(x), float(y)),
|
|
74
76
|
size=Size2D(int(width), int(height)),
|
|
75
77
|
velocity=Velocity2D(float(vx), float(vy)),
|
|
78
|
+
time_scale=time_scale,
|
|
76
79
|
color=color,
|
|
77
80
|
)
|
|
78
81
|
|
mini_arcade_core/ui/__init__.py
CHANGED
|
@@ -5,10 +5,22 @@ Includes buttons, labels, and layout management.
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
from .menu import
|
|
8
|
+
from .menu import (
|
|
9
|
+
BaseMenuScene,
|
|
10
|
+
Menu,
|
|
11
|
+
MenuItem,
|
|
12
|
+
MenuModel,
|
|
13
|
+
MenuStyle,
|
|
14
|
+
MenuSystem,
|
|
15
|
+
)
|
|
16
|
+
from .overlays import BaseOverlay
|
|
9
17
|
|
|
10
18
|
__all__ = [
|
|
11
19
|
"Menu",
|
|
12
20
|
"MenuItem",
|
|
13
21
|
"MenuStyle",
|
|
22
|
+
"MenuModel",
|
|
23
|
+
"MenuSystem",
|
|
24
|
+
"BaseMenuScene",
|
|
25
|
+
"BaseOverlay",
|
|
14
26
|
]
|