mini-arcade-core 0.9.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 +100 -0
- mini_arcade_core/backend.py +225 -0
- mini_arcade_core/boundaries2d.py +107 -0
- mini_arcade_core/collision2d.py +71 -0
- mini_arcade_core/entity.py +68 -0
- mini_arcade_core/game.py +243 -0
- mini_arcade_core/geometry2d.py +69 -0
- mini_arcade_core/kinematics2d.py +78 -0
- mini_arcade_core/physics2d.py +73 -0
- mini_arcade_core/scene.py +147 -0
- mini_arcade_core/ui/__init__.py +0 -0
- mini_arcade_core/ui/menu.py +135 -0
- mini_arcade_core-0.9.0.dist-info/METADATA +288 -0
- mini_arcade_core-0.9.0.dist-info/RECORD +16 -0
- mini_arcade_core-0.9.0.dist-info/WHEEL +4 -0
- mini_arcade_core-0.9.0.dist-info/licenses/LICENSE +19 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Entry point for the mini_arcade_core package.
|
|
3
|
+
Provides access to core classes and a convenience function to run a game.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
10
|
+
|
|
11
|
+
from .backend import Backend, Event, EventType
|
|
12
|
+
from .boundaries2d import (
|
|
13
|
+
RectKinematic,
|
|
14
|
+
RectSprite,
|
|
15
|
+
VerticalBounce,
|
|
16
|
+
VerticalWrap,
|
|
17
|
+
)
|
|
18
|
+
from .collision2d import RectCollider
|
|
19
|
+
from .entity import Entity, KinematicEntity, SpriteEntity
|
|
20
|
+
from .game import Game, GameConfig
|
|
21
|
+
from .geometry2d import Bounds2D, Position2D, Size2D
|
|
22
|
+
from .kinematics2d import KinematicData
|
|
23
|
+
from .physics2d import Velocity2D
|
|
24
|
+
from .scene import Scene
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def run_game(initial_scene_cls: type[Scene], config: GameConfig | None = None):
|
|
30
|
+
"""
|
|
31
|
+
Convenience helper to bootstrap and run a game with a single scene.
|
|
32
|
+
|
|
33
|
+
:param initial_scene_cls: The Scene subclass to instantiate as the initial scene.
|
|
34
|
+
:type initial_scene_cls: type[Scene]
|
|
35
|
+
|
|
36
|
+
:param config: Optional GameConfig to customize game settings.
|
|
37
|
+
:type config: GameConfig | None
|
|
38
|
+
|
|
39
|
+
:raises ValueError: If the provided config does not have a valid Backend.
|
|
40
|
+
"""
|
|
41
|
+
cfg = config or GameConfig()
|
|
42
|
+
if config.backend is None:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
"GameConfig.backend must be set to a Backend instance"
|
|
45
|
+
)
|
|
46
|
+
game = Game(cfg)
|
|
47
|
+
scene = initial_scene_cls(game)
|
|
48
|
+
game.run(scene)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
"Game",
|
|
53
|
+
"GameConfig",
|
|
54
|
+
"Scene",
|
|
55
|
+
"Entity",
|
|
56
|
+
"SpriteEntity",
|
|
57
|
+
"run_game",
|
|
58
|
+
"Backend",
|
|
59
|
+
"Event",
|
|
60
|
+
"EventType",
|
|
61
|
+
"Velocity2D",
|
|
62
|
+
"Position2D",
|
|
63
|
+
"Size2D",
|
|
64
|
+
"KinematicEntity",
|
|
65
|
+
"KinematicData",
|
|
66
|
+
"RectCollider",
|
|
67
|
+
"VerticalBounce",
|
|
68
|
+
"Bounds2D",
|
|
69
|
+
"VerticalWrap",
|
|
70
|
+
"RectSprite",
|
|
71
|
+
"RectKinematic",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
PACKAGE_NAME = "mini-arcade-core" # or whatever is in your pyproject.toml
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_version() -> str:
|
|
78
|
+
"""
|
|
79
|
+
Return the installed package version.
|
|
80
|
+
|
|
81
|
+
This is a thin helper around importlib.metadata.version so games can do:
|
|
82
|
+
|
|
83
|
+
from mini_arcade_core import get_version
|
|
84
|
+
print(get_version())
|
|
85
|
+
|
|
86
|
+
:return: The version string of the installed package.
|
|
87
|
+
:rtype: str
|
|
88
|
+
|
|
89
|
+
:raises PackageNotFoundError: If the package is not installed.
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
return version(PACKAGE_NAME)
|
|
93
|
+
except PackageNotFoundError: # if running from source / editable
|
|
94
|
+
logger.warning(
|
|
95
|
+
f"Package '{PACKAGE_NAME}' not found. Returning default version '0.0.0'."
|
|
96
|
+
)
|
|
97
|
+
return "0.0.0"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
__version__ = get_version()
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backend interface for rendering and input.
|
|
3
|
+
This is the only part of the code that talks to SDL/pygame directly.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from enum import Enum, auto
|
|
10
|
+
from typing import Iterable, Optional, Protocol, Tuple, Union
|
|
11
|
+
|
|
12
|
+
Color = Union[Tuple[int, int, int], Tuple[int, int, int, int]]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EventType(Enum):
|
|
16
|
+
"""
|
|
17
|
+
High-level event types understood by the core.
|
|
18
|
+
|
|
19
|
+
:cvar UNKNOWN: Unknown/unhandled event.
|
|
20
|
+
:cvar QUIT: User requested to quit the game.
|
|
21
|
+
:cvar KEYDOWN: A key was pressed.
|
|
22
|
+
:cvar KEYUP: A key was released.
|
|
23
|
+
:cvar MOUSEMOTION: The mouse was moved.
|
|
24
|
+
:cvar MOUSEBUTTONDOWN: A mouse button was pressed.
|
|
25
|
+
:cvar MOUSEBUTTONUP: A mouse button was released.
|
|
26
|
+
:cvar MOUSEWHEEL: The mouse wheel was scrolled.
|
|
27
|
+
:cvar WINDOWRESIZED: The window was resized.
|
|
28
|
+
:cvar TEXTINPUT: Text input event (for IME support).
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
UNKNOWN = auto()
|
|
32
|
+
QUIT = auto()
|
|
33
|
+
|
|
34
|
+
KEYDOWN = auto()
|
|
35
|
+
KEYUP = auto()
|
|
36
|
+
|
|
37
|
+
# Mouse
|
|
38
|
+
MOUSEMOTION = auto()
|
|
39
|
+
MOUSEBUTTONDOWN = auto()
|
|
40
|
+
MOUSEBUTTONUP = auto()
|
|
41
|
+
MOUSEWHEEL = auto()
|
|
42
|
+
|
|
43
|
+
# Window / text
|
|
44
|
+
WINDOWRESIZED = auto()
|
|
45
|
+
TEXTINPUT = auto()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Justification: Simple data container for now
|
|
49
|
+
# pylint: disable=too-many-instance-attributes
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class Event:
|
|
52
|
+
"""
|
|
53
|
+
Core event type.
|
|
54
|
+
|
|
55
|
+
For now we only care about:
|
|
56
|
+
- type: what happened
|
|
57
|
+
- key: integer key code (e.g. ESC = 27), or None if not applicable
|
|
58
|
+
|
|
59
|
+
:ivar type (EventType): The type of event.
|
|
60
|
+
:ivar key (int | None): The key code associated with the event, if any.
|
|
61
|
+
:ivar scancode (int | None): The hardware scancode of the key, if any.
|
|
62
|
+
:ivar mod (int | None): Modifier keys bitmask, if any.
|
|
63
|
+
:ivar repeat (bool | None): Whether this key event is a repeat, if any.
|
|
64
|
+
:ivar x (int | None): Mouse X position, if any.
|
|
65
|
+
:ivar y (int | None): Mouse Y position, if any.
|
|
66
|
+
:ivar dx (int | None): Mouse delta X, if any.
|
|
67
|
+
:ivar dy (int | None): Mouse delta Y, if any.
|
|
68
|
+
:ivar button (int | None): Mouse button number, if any.
|
|
69
|
+
:ivar wheel (Tuple[int, int] | None): Mouse wheel scroll (x, y), if any.
|
|
70
|
+
:ivar size (Tuple[int, int] | None): New window size (width, height), if any.
|
|
71
|
+
:ivar text (str | None): Text input, if any.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
type: EventType
|
|
75
|
+
key: Optional[int] = None
|
|
76
|
+
|
|
77
|
+
# Keyboard extras (optional)
|
|
78
|
+
scancode: Optional[int] = None
|
|
79
|
+
mod: Optional[int] = None
|
|
80
|
+
repeat: Optional[bool] = None
|
|
81
|
+
|
|
82
|
+
# Mouse (optional)
|
|
83
|
+
x: Optional[int] = None
|
|
84
|
+
y: Optional[int] = None
|
|
85
|
+
dx: Optional[int] = None
|
|
86
|
+
dy: Optional[int] = None
|
|
87
|
+
button: Optional[int] = None
|
|
88
|
+
wheel: Optional[Tuple[int, int]] = None # (wheel_x, wheel_y)
|
|
89
|
+
|
|
90
|
+
# Window (optional)
|
|
91
|
+
size: Optional[Tuple[int, int]] = None # (width, height)
|
|
92
|
+
|
|
93
|
+
# Text input (optional)
|
|
94
|
+
text: Optional[str] = None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# pylint: enable=too-many-instance-attributes
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Backend(Protocol):
|
|
101
|
+
"""
|
|
102
|
+
Interface that any rendering/input backend must implement.
|
|
103
|
+
|
|
104
|
+
mini-arcade-core only talks to this protocol, never to SDL/pygame directly.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def init(self, width: int, height: int, title: str):
|
|
108
|
+
"""
|
|
109
|
+
Initialize the backend and open a window.
|
|
110
|
+
Should be called once before the main loop.
|
|
111
|
+
|
|
112
|
+
:param width: Width of the window in pixels.
|
|
113
|
+
:type width: int
|
|
114
|
+
|
|
115
|
+
:param height: Height of the window in pixels.
|
|
116
|
+
:type height: int
|
|
117
|
+
|
|
118
|
+
:param title: Title of the window.
|
|
119
|
+
:type title: str
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def poll_events(self) -> Iterable[Event]:
|
|
123
|
+
"""
|
|
124
|
+
Return all pending events since last call.
|
|
125
|
+
Concrete backends will translate their native events into core Event objects.
|
|
126
|
+
|
|
127
|
+
:return: An iterable of Event objects.
|
|
128
|
+
:rtype: Iterable[Event]
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
def set_clear_color(self, r: int, g: int, b: int):
|
|
132
|
+
"""
|
|
133
|
+
Set the background/clear color used by begin_frame.
|
|
134
|
+
|
|
135
|
+
:param r: Red component (0-255).
|
|
136
|
+
:type r: int
|
|
137
|
+
|
|
138
|
+
:param g: Green component (0-255).
|
|
139
|
+
:type g: int
|
|
140
|
+
|
|
141
|
+
:param b: Blue component (0-255).
|
|
142
|
+
:type b: int
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
def begin_frame(self):
|
|
146
|
+
"""
|
|
147
|
+
Prepare for drawing a new frame (e.g. clear screen).
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
def end_frame(self):
|
|
151
|
+
"""
|
|
152
|
+
Present the frame to the user (swap buffers).
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
# Justification: Simple drawing API for now
|
|
156
|
+
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
157
|
+
def draw_rect(
|
|
158
|
+
self,
|
|
159
|
+
x: int,
|
|
160
|
+
y: int,
|
|
161
|
+
w: int,
|
|
162
|
+
h: int,
|
|
163
|
+
color: Color = (255, 255, 255),
|
|
164
|
+
):
|
|
165
|
+
"""
|
|
166
|
+
Draw a filled rectangle in some default color.
|
|
167
|
+
We'll keep this minimal for now; later we can extend with colors/sprites.
|
|
168
|
+
|
|
169
|
+
:param x: X position of the rectangle's top-left corner.
|
|
170
|
+
:type x: int
|
|
171
|
+
|
|
172
|
+
:param y: Y position of the rectangle's top-left corner.
|
|
173
|
+
:type y: int
|
|
174
|
+
|
|
175
|
+
:param w: Width of the rectangle.
|
|
176
|
+
:type w: int
|
|
177
|
+
|
|
178
|
+
:param h: Height of the rectangle.
|
|
179
|
+
:type h: int
|
|
180
|
+
|
|
181
|
+
:param color: RGB color tuple.
|
|
182
|
+
:type color: Color
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
# pylint: enable=too-many-arguments,too-many-positional-arguments
|
|
186
|
+
|
|
187
|
+
def draw_text(
|
|
188
|
+
self,
|
|
189
|
+
x: int,
|
|
190
|
+
y: int,
|
|
191
|
+
text: str,
|
|
192
|
+
color: Color = (255, 255, 255),
|
|
193
|
+
):
|
|
194
|
+
"""
|
|
195
|
+
Draw text at the given position in a default font and color.
|
|
196
|
+
|
|
197
|
+
Backends may ignore advanced styling for now; this is just to render
|
|
198
|
+
simple labels like menu items, scores, etc.
|
|
199
|
+
|
|
200
|
+
:param x: X position of the text's top-left corner.
|
|
201
|
+
:type x: int
|
|
202
|
+
|
|
203
|
+
:param y: Y position of the text's top-left corner.
|
|
204
|
+
:type y: int
|
|
205
|
+
|
|
206
|
+
:param text: The text string to draw.
|
|
207
|
+
:type text: str
|
|
208
|
+
|
|
209
|
+
:param color: RGB color tuple.
|
|
210
|
+
:type color: Color
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
def capture_frame(self, path: str | None = None) -> bytes | None:
|
|
214
|
+
"""
|
|
215
|
+
Capture the current frame.
|
|
216
|
+
If `path` is provided, save to that file (e.g. PNG).
|
|
217
|
+
Returns raw bytes (PNG) or None if unsupported.
|
|
218
|
+
|
|
219
|
+
:param path: Optional file path to save the screenshot.
|
|
220
|
+
:type path: str | None
|
|
221
|
+
|
|
222
|
+
:return: Raw image bytes if no path given, else None.
|
|
223
|
+
:rtype: bytes | None
|
|
224
|
+
"""
|
|
225
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Boundary behaviors for 2D rectangular objects.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Protocol
|
|
9
|
+
|
|
10
|
+
from .geometry2d import Bounds2D, Position2D, Size2D
|
|
11
|
+
from .physics2d import Velocity2D
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RectKinematic(Protocol):
|
|
15
|
+
"""
|
|
16
|
+
Minimal contract for something that can bounce:
|
|
17
|
+
- position: Position2D
|
|
18
|
+
- size: Size2D
|
|
19
|
+
- velocity: Velocity2D
|
|
20
|
+
|
|
21
|
+
:ivar position (Position2D): Top-left position of the object.
|
|
22
|
+
:ivar size (Size2D): Size of the object.
|
|
23
|
+
:ivar velocity (Velocity2D): Velocity of the object.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
position: Position2D
|
|
27
|
+
size: Size2D
|
|
28
|
+
velocity: Velocity2D
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class VerticalBounce:
|
|
33
|
+
"""
|
|
34
|
+
Bounce an object off the top/bottom bounds by inverting vy
|
|
35
|
+
and clamping its position inside the bounds.
|
|
36
|
+
|
|
37
|
+
:ivar bounds (Bounds2D): The boundary rectangle.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
bounds: Bounds2D
|
|
41
|
+
|
|
42
|
+
def apply(self, obj: RectKinematic):
|
|
43
|
+
"""
|
|
44
|
+
Apply vertical bounce to the given object.
|
|
45
|
+
|
|
46
|
+
:param obj: The object to apply the bounce to.
|
|
47
|
+
:type obj: RectKinematic
|
|
48
|
+
"""
|
|
49
|
+
top = self.bounds.top
|
|
50
|
+
bottom = self.bounds.bottom
|
|
51
|
+
|
|
52
|
+
# Top collision
|
|
53
|
+
if obj.position.y <= top:
|
|
54
|
+
obj.position.y = top
|
|
55
|
+
obj.velocity.vy *= -1
|
|
56
|
+
|
|
57
|
+
# Bottom collision
|
|
58
|
+
if obj.position.y + obj.size.height >= bottom:
|
|
59
|
+
obj.position.y = bottom - obj.size.height
|
|
60
|
+
obj.velocity.vy *= -1
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class RectSprite(Protocol):
|
|
64
|
+
"""
|
|
65
|
+
Minimal contract for something that can wrap:
|
|
66
|
+
- position: Position2D
|
|
67
|
+
- size: Size2D
|
|
68
|
+
(no velocity required)
|
|
69
|
+
|
|
70
|
+
:ivar position (Position2D): Top-left position of the object.
|
|
71
|
+
:ivar size (Size2D): Size of the object.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
position: Position2D
|
|
75
|
+
size: Size2D
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class VerticalWrap:
|
|
80
|
+
"""
|
|
81
|
+
Wrap an object top <-> bottom.
|
|
82
|
+
|
|
83
|
+
If it leaves above the top, it reappears at the bottom.
|
|
84
|
+
If it leaves below the bottom, it reappears at the top.
|
|
85
|
+
|
|
86
|
+
:ivar bounds (Bounds2D): The boundary rectangle.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
bounds: Bounds2D
|
|
90
|
+
|
|
91
|
+
def apply(self, obj: RectSprite):
|
|
92
|
+
"""
|
|
93
|
+
Apply vertical wrap to the given object.
|
|
94
|
+
|
|
95
|
+
:param obj: The object to apply the wrap to.
|
|
96
|
+
:type obj: RectSprite
|
|
97
|
+
"""
|
|
98
|
+
top = self.bounds.top
|
|
99
|
+
bottom = self.bounds.bottom
|
|
100
|
+
|
|
101
|
+
# Completely above top → appear at bottom
|
|
102
|
+
if obj.position.y + obj.size.height < top:
|
|
103
|
+
obj.position.y = bottom
|
|
104
|
+
|
|
105
|
+
# Completely below bottom → appear at top
|
|
106
|
+
elif obj.position.y > bottom:
|
|
107
|
+
obj.position.y = top - obj.size.height
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
2D collision detection helpers.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from .geometry2d import Position2D, Size2D
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _rects_intersect(
|
|
13
|
+
pos_a: Position2D,
|
|
14
|
+
size_a: Size2D,
|
|
15
|
+
pos_b: Position2D,
|
|
16
|
+
size_b: Size2D,
|
|
17
|
+
) -> bool:
|
|
18
|
+
"""
|
|
19
|
+
Low-level AABB check. Internal helper.
|
|
20
|
+
|
|
21
|
+
:param pos_a: Top-left position of rectangle A.
|
|
22
|
+
:type pos_a: Position2D
|
|
23
|
+
|
|
24
|
+
:param size_a: Size of rectangle A.
|
|
25
|
+
:type size_a: Size2D
|
|
26
|
+
|
|
27
|
+
:param pos_b: Top-left position of rectangle B.
|
|
28
|
+
:type pos_b: Position2D
|
|
29
|
+
|
|
30
|
+
:param size_b: Size of rectangle B.
|
|
31
|
+
:type size_b: Size2D
|
|
32
|
+
|
|
33
|
+
:return: True if the rectangles intersect.
|
|
34
|
+
:rtype: bool
|
|
35
|
+
"""
|
|
36
|
+
return not (
|
|
37
|
+
pos_a.x + size_a.width < pos_b.x
|
|
38
|
+
or pos_a.x > pos_b.x + size_b.width
|
|
39
|
+
or pos_a.y + size_a.height < pos_b.y
|
|
40
|
+
or pos_a.y > pos_b.y + size_b.height
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class RectCollider:
|
|
46
|
+
"""
|
|
47
|
+
OOP collision helper that wraps a Position2D + Size2D pair.
|
|
48
|
+
|
|
49
|
+
It does NOT own the data – it just points to them. If the
|
|
50
|
+
entity moves (position changes), the collider “sees” it.
|
|
51
|
+
|
|
52
|
+
:ivar position (Position2D): Top-left position of the rectangle.
|
|
53
|
+
:ivar size (Size2D): Size of the rectangle.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
position: Position2D
|
|
57
|
+
size: Size2D
|
|
58
|
+
|
|
59
|
+
def intersects(self, other: "RectCollider") -> bool:
|
|
60
|
+
"""
|
|
61
|
+
High-level OOP method to check collision with another collider.
|
|
62
|
+
|
|
63
|
+
;param other: The other rectangle collider.
|
|
64
|
+
:type other: RectCollider
|
|
65
|
+
|
|
66
|
+
:return: True if the rectangles intersect.
|
|
67
|
+
:rtype: bool
|
|
68
|
+
"""
|
|
69
|
+
return _rects_intersect(
|
|
70
|
+
self.position, self.size, other.position, other.size
|
|
71
|
+
)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Entity base classes for mini_arcade_core.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .collision2d import RectCollider
|
|
10
|
+
from .geometry2d import Position2D, Size2D
|
|
11
|
+
from .kinematics2d import KinematicData
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Entity:
|
|
15
|
+
"""Entity base class for game objects."""
|
|
16
|
+
|
|
17
|
+
def update(self, dt: float):
|
|
18
|
+
"""
|
|
19
|
+
Advance the entity state by ``dt`` seconds.
|
|
20
|
+
|
|
21
|
+
:param dt: Time delta in seconds.
|
|
22
|
+
:type dt: float
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def draw(self, surface: Any):
|
|
26
|
+
"""
|
|
27
|
+
Render the entity to the given surface.
|
|
28
|
+
|
|
29
|
+
:param surface: The surface to draw on.
|
|
30
|
+
:type surface: Any
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SpriteEntity(Entity):
|
|
35
|
+
"""Entity with position and size."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, position: Position2D, size: Size2D):
|
|
38
|
+
"""
|
|
39
|
+
:param position: Top-left position of the entity.
|
|
40
|
+
:type position: Position2D
|
|
41
|
+
|
|
42
|
+
:param size: Size of the entity.
|
|
43
|
+
:type size: Size2D
|
|
44
|
+
"""
|
|
45
|
+
self.position = Position2D(float(position.x), float(position.y))
|
|
46
|
+
self.size = Size2D(int(size.width), int(size.height))
|
|
47
|
+
self.collider = RectCollider(self.position, self.size)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class KinematicEntity(SpriteEntity):
|
|
51
|
+
"""SpriteEntity with velocity-based movement."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, kinematic_data: KinematicData):
|
|
54
|
+
"""
|
|
55
|
+
:param kinematic_data: Kinematic data for the entity.
|
|
56
|
+
:type kinematic_data: KinematicData
|
|
57
|
+
"""
|
|
58
|
+
super().__init__(
|
|
59
|
+
position=kinematic_data.position,
|
|
60
|
+
size=kinematic_data.size,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
self.velocity = kinematic_data.velocity
|
|
64
|
+
|
|
65
|
+
def update(self, dt: float):
|
|
66
|
+
self.position.x, self.position.y = self.velocity.advance(
|
|
67
|
+
self.position.x, self.position.y, dt
|
|
68
|
+
)
|