tilemap-parser 5.0.3__tar.gz → 5.0.4__tar.gz
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.
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/PKG-INFO +1 -1
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/pyproject.toml +1 -1
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/parser/collision.py +53 -3
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/parser/collision_loader.py +4 -1
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/animation_player.py +49 -8
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/collision_cache.py +12 -6
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/particles.py +18 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser.egg-info/PKG-INFO +1 -1
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_collision.py +121 -1
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_integration_animation.py +125 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/LICENSE +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/README.md +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/pypi-readme.md +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/setup.cfg +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/__init__.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/parser/__init__.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/parser/animation.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/parser/map_parse.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/parser/node_parse.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/parser/particle.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/parser/tmx_converter.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/__init__.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/area_node.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/body.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/camera.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/collision/__init__.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/collision/hit.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/collision/manager.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/collision/shapes.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/map_loader.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/map_object.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/movement/__init__.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/movement/grounded.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/movement/platformer.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/movement/queries.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/movement/rpg.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/movement/runner.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/movement/slide.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/movement/types.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/navigation/__init__.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/navigation/nav_grid.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/navigation/path_follower.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/navigation/pathfinder.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/object_collision.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/polygon_query.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/protocols.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/renderer.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/tile_collision.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/world.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/utils/__init__.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/utils/geometry.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser.egg-info/SOURCES.txt +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser.egg-info/dependency_links.txt +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser.egg-info/requires.txt +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser.egg-info/top_level.txt +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_body.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_camera.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_geometry.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_gid_collision.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_integration_collision.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_integration_map_loader.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_map_loader.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_map_object.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_move_grounded.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_navigation.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_object_collision.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_object_surfaces.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_particle_emitter.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_render_scale.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_tile_collision.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_tile_layer_renderer_y_sort.py +0 -0
- {tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/tests/test_tmx_converter.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tilemap-parser
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.4
|
|
4
4
|
Summary: Standalone parser/loader for tilemap-editor JSON maps, sprite animations, and collision detection runtime.
|
|
5
5
|
Author: tilemap parser contributors
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tilemap-parser"
|
|
7
|
-
version = "5.0.
|
|
7
|
+
version = "5.0.4"
|
|
8
8
|
description = "Standalone parser/loader for tilemap-editor JSON maps, sprite animations, and collision detection runtime."
|
|
9
9
|
readme = "pypi-readme.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -9,6 +9,7 @@ Provides parsing of collision data from JSON into data classes:
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
import math
|
|
12
13
|
from dataclasses import dataclass, field
|
|
13
14
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
14
15
|
|
|
@@ -34,6 +35,13 @@ class CollisionPolygon:
|
|
|
34
35
|
world_vertices = [(tile_x + vx * scale, tile_y + vy * scale) for vx, vy in self.vertices]
|
|
35
36
|
return CollisionPolygon(vertices=world_vertices, one_way=self.one_way)
|
|
36
37
|
|
|
38
|
+
def scaled(self, scale: float) -> "CollisionPolygon":
|
|
39
|
+
"""Return a copy with all vertices multiplied by *scale*."""
|
|
40
|
+
return CollisionPolygon(
|
|
41
|
+
vertices=[(vx * scale, vy * scale) for vx, vy in self.vertices],
|
|
42
|
+
one_way=self.one_way,
|
|
43
|
+
)
|
|
44
|
+
|
|
37
45
|
def is_valid(self) -> bool:
|
|
38
46
|
"""Check if polygon has at least 3 vertices"""
|
|
39
47
|
return len(self.vertices) >= 3
|
|
@@ -130,6 +138,14 @@ class RectangleShape:
|
|
|
130
138
|
top = y + self.offset[1]
|
|
131
139
|
return (left, top, left + self.width, top + self.height)
|
|
132
140
|
|
|
141
|
+
def scaled(self, scale: float) -> "RectangleShape":
|
|
142
|
+
"""Return a copy with dimensions and offset multiplied by *scale*."""
|
|
143
|
+
return RectangleShape(
|
|
144
|
+
width=self.width * scale,
|
|
145
|
+
height=self.height * scale,
|
|
146
|
+
offset=(self.offset[0] * scale, self.offset[1] * scale),
|
|
147
|
+
)
|
|
148
|
+
|
|
133
149
|
|
|
134
150
|
@dataclass
|
|
135
151
|
class CircleShape:
|
|
@@ -142,6 +158,13 @@ class CircleShape:
|
|
|
142
158
|
"""Get center position in world space"""
|
|
143
159
|
return (x + self.offset[0], y + self.offset[1])
|
|
144
160
|
|
|
161
|
+
def scaled(self, scale: float) -> "CircleShape":
|
|
162
|
+
"""Return a copy with radius and offset multiplied by *scale*."""
|
|
163
|
+
return CircleShape(
|
|
164
|
+
radius=self.radius * scale,
|
|
165
|
+
offset=(self.offset[0] * scale, self.offset[1] * scale),
|
|
166
|
+
)
|
|
167
|
+
|
|
145
168
|
|
|
146
169
|
@dataclass
|
|
147
170
|
class CapsuleShape:
|
|
@@ -159,6 +182,14 @@ class CapsuleShape:
|
|
|
159
182
|
"""Get bottom circle center in world space"""
|
|
160
183
|
return (x + self.offset[0], y + self.offset[1] + self.height)
|
|
161
184
|
|
|
185
|
+
def scaled(self, scale: float) -> "CapsuleShape":
|
|
186
|
+
"""Return a copy with radius, height, and offset multiplied by *scale*."""
|
|
187
|
+
return CapsuleShape(
|
|
188
|
+
radius=self.radius * scale,
|
|
189
|
+
height=self.height * scale,
|
|
190
|
+
offset=(self.offset[0] * scale, self.offset[1] * scale),
|
|
191
|
+
)
|
|
192
|
+
|
|
162
193
|
|
|
163
194
|
CharacterShapeType = Union[RectangleShape, CircleShape, CapsuleShape, CollisionPolygon]
|
|
164
195
|
|
|
@@ -173,6 +204,16 @@ class CharacterCollision:
|
|
|
173
204
|
collision_layer: int = 1
|
|
174
205
|
collision_mask: int = 0xFFFFFFFF
|
|
175
206
|
|
|
207
|
+
def scaled(self, scale: float) -> "CharacterCollision":
|
|
208
|
+
"""Return a copy whose shape is scaled by *scale* (typically ``render_scale``)."""
|
|
209
|
+
return CharacterCollision(
|
|
210
|
+
name=self.name,
|
|
211
|
+
shape=self.shape.scaled(scale),
|
|
212
|
+
properties=dict(self.properties),
|
|
213
|
+
collision_layer=self.collision_layer,
|
|
214
|
+
collision_mask=self.collision_mask,
|
|
215
|
+
)
|
|
216
|
+
|
|
176
217
|
|
|
177
218
|
@dataclass
|
|
178
219
|
class ObjectCollisionRegionData:
|
|
@@ -255,19 +296,28 @@ def parse_tileset_collision(data: JsonDict) -> TilesetCollision:
|
|
|
255
296
|
raise CollisionParseError(f"Invalid tileset collision data: {e}") from e
|
|
256
297
|
|
|
257
298
|
|
|
258
|
-
def parse_character_collision(data: JsonDict) -> CharacterCollision:
|
|
299
|
+
def parse_character_collision(data: JsonDict, render_scale: float = 1.0) -> CharacterCollision:
|
|
259
300
|
"""
|
|
260
301
|
Parse character collision data from dictionary.
|
|
261
302
|
|
|
262
303
|
Args:
|
|
263
304
|
data: Dictionary loaded from .collision.json file
|
|
305
|
+
render_scale: Multiplier applied to shape dimensions and offsets
|
|
306
|
+
(no-op when 1.0).
|
|
264
307
|
|
|
265
308
|
Returns:
|
|
266
309
|
CharacterCollision object
|
|
267
310
|
|
|
268
311
|
Raises:
|
|
269
|
-
CollisionParseError: If data format is invalid
|
|
312
|
+
CollisionParseError: If data format is invalid or render_scale is not
|
|
313
|
+
finite and greater than zero.
|
|
270
314
|
"""
|
|
315
|
+
if (
|
|
316
|
+
not isinstance(render_scale, (int, float))
|
|
317
|
+
or not math.isfinite(render_scale)
|
|
318
|
+
or render_scale <= 0
|
|
319
|
+
):
|
|
320
|
+
raise CollisionParseError(f"render_scale must be finite and > 0, got {render_scale!r}")
|
|
271
321
|
try:
|
|
272
322
|
name = data["name"]
|
|
273
323
|
shape_data = data["shape"]
|
|
@@ -312,7 +362,7 @@ def parse_character_collision(data: JsonDict) -> CharacterCollision:
|
|
|
312
362
|
properties=properties,
|
|
313
363
|
collision_layer=collision_layer,
|
|
314
364
|
collision_mask=collision_mask,
|
|
315
|
-
)
|
|
365
|
+
).scaled(render_scale)
|
|
316
366
|
except (KeyError, ValueError, TypeError) as e:
|
|
317
367
|
raise CollisionParseError(f"Invalid character collision data: {e}") from e
|
|
318
368
|
|
|
@@ -55,6 +55,7 @@ def load_tileset_collision(
|
|
|
55
55
|
|
|
56
56
|
def load_character_collision(
|
|
57
57
|
collision_path: Union[str, Path],
|
|
58
|
+
render_scale: float = 1.0,
|
|
58
59
|
) -> CharacterCollision | None:
|
|
59
60
|
"""
|
|
60
61
|
Load character collision data from a collision JSON file.
|
|
@@ -64,6 +65,8 @@ def load_character_collision(
|
|
|
64
65
|
|
|
65
66
|
Args:
|
|
66
67
|
collision_path: Direct path to the .collision.json file.
|
|
68
|
+
render_scale: Multiplier applied to shape dimensions and offsets
|
|
69
|
+
(no-op when 1.0).
|
|
67
70
|
|
|
68
71
|
Returns:
|
|
69
72
|
CharacterCollision object, or None if the file does not exist.
|
|
@@ -79,7 +82,7 @@ def load_character_collision(
|
|
|
79
82
|
try:
|
|
80
83
|
with open(collision_path, "r", encoding="utf-8") as f:
|
|
81
84
|
data = json.load(f)
|
|
82
|
-
return parse_character_collision(data)
|
|
85
|
+
return parse_character_collision(data, render_scale=render_scale)
|
|
83
86
|
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as e:
|
|
84
87
|
raise CollisionParseError(f"Cannot load {collision_path}: {e}") from e
|
|
85
88
|
|
{tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/animation_player.py
RENAMED
|
@@ -20,6 +20,9 @@ class SpriteAnimationSet:
|
|
|
20
20
|
json_path: Optional[Path] = None
|
|
21
21
|
grid_offset_x: int = 0
|
|
22
22
|
grid_offset_y: int = 0
|
|
23
|
+
render_scale: float = 1.0
|
|
24
|
+
grid_columns: int = 0
|
|
25
|
+
grid_rows: int = 0
|
|
23
26
|
|
|
24
27
|
@classmethod
|
|
25
28
|
def load(
|
|
@@ -28,6 +31,7 @@ class SpriteAnimationSet:
|
|
|
28
31
|
*,
|
|
29
32
|
spritesheet_path: Optional[PathLike] = None,
|
|
30
33
|
extra_search_base: Optional[Path] = None,
|
|
34
|
+
render_scale: float = 1.0,
|
|
31
35
|
) -> "SpriteAnimationSet":
|
|
32
36
|
path = Path(json_path)
|
|
33
37
|
library = parse_animation_file(path)
|
|
@@ -59,6 +63,29 @@ class SpriteAnimationSet:
|
|
|
59
63
|
except pygame.error as e:
|
|
60
64
|
raise AnimationParseError(f"Failed to load image {image_path}: {e}") from e
|
|
61
65
|
|
|
66
|
+
tw, th = library.tile_size
|
|
67
|
+
ox, oy = library.grid_offset
|
|
68
|
+
grid_columns = max(1, (surface.get_width() - ox) // tw)
|
|
69
|
+
grid_rows = max(1, (surface.get_height() - oy) // th)
|
|
70
|
+
|
|
71
|
+
if render_scale != 1.0:
|
|
72
|
+
if render_scale <= 0:
|
|
73
|
+
raise ValueError(f"render_scale must be > 0, got {render_scale}")
|
|
74
|
+
w, h = surface.get_size()
|
|
75
|
+
sw, sh = int(w * render_scale), int(h * render_scale)
|
|
76
|
+
stw, sth = int(tw * render_scale), int(th * render_scale)
|
|
77
|
+
if sw < 1 or sh < 1 or stw < 1 or sth < 1:
|
|
78
|
+
raise ValueError(f"render_scale {render_scale} produces zero-sized atlas cells")
|
|
79
|
+
if (
|
|
80
|
+
int(ox * render_scale) + grid_columns * stw > sw
|
|
81
|
+
or int(oy * render_scale) + grid_rows * sth > sh
|
|
82
|
+
):
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"render_scale {render_scale} cannot maintain atlas grid cell bounds"
|
|
85
|
+
)
|
|
86
|
+
surface = pygame.transform.scale(surface, (sw, sh))
|
|
87
|
+
library = _scaled_library(library, render_scale)
|
|
88
|
+
|
|
62
89
|
return cls(
|
|
63
90
|
library=library,
|
|
64
91
|
surface=surface,
|
|
@@ -66,6 +93,9 @@ class SpriteAnimationSet:
|
|
|
66
93
|
json_path=path,
|
|
67
94
|
grid_offset_x=library.grid_offset[0],
|
|
68
95
|
grid_offset_y=library.grid_offset[1],
|
|
96
|
+
render_scale=render_scale,
|
|
97
|
+
grid_columns=grid_columns,
|
|
98
|
+
grid_rows=grid_rows,
|
|
69
99
|
)
|
|
70
100
|
|
|
71
101
|
def get_content_bounds(self, clip_name: str) -> Optional[Rect]:
|
|
@@ -88,15 +118,10 @@ class SpriteAnimationSet:
|
|
|
88
118
|
|
|
89
119
|
def get_image(self, variant_id: int, *, copy_surface: bool = True) -> Optional[Surface]:
|
|
90
120
|
tw, th = self.library.tile_size
|
|
91
|
-
if tw <= 0 or th <= 0:
|
|
92
|
-
return None
|
|
93
|
-
available_w = self.surface.get_width() - self.grid_offset_x
|
|
94
|
-
available_h = self.surface.get_height() - self.grid_offset_y
|
|
95
|
-
if available_w < tw or available_h < th:
|
|
121
|
+
if tw <= 0 or th <= 0 or self.grid_columns <= 0 or self.grid_rows <= 0:
|
|
96
122
|
return None
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
row = variant_id // cols
|
|
123
|
+
col = variant_id % self.grid_columns
|
|
124
|
+
row = variant_id // self.grid_columns
|
|
100
125
|
src = Rect(self.grid_offset_x + col * tw, self.grid_offset_y + row * th, tw, th)
|
|
101
126
|
if not self.surface.get_rect().contains(src):
|
|
102
127
|
return None
|
|
@@ -161,3 +186,19 @@ class AnimationPlayer:
|
|
|
161
186
|
if variant not in self._frame_cache:
|
|
162
187
|
self._frame_cache[variant] = self.animation_set.get_image(variant, copy_surface=True)
|
|
163
188
|
return self._frame_cache[variant]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _scaled_library(library: AnimationLibrary, scale: float) -> AnimationLibrary:
|
|
192
|
+
"""Return a copy of *library* whose grid geometry is scaled by *scale*.
|
|
193
|
+
|
|
194
|
+
Kept in lockstep with the scaled spritesheet surface so that
|
|
195
|
+
:meth:`SpriteAnimationSet.get_image` continues to address cells
|
|
196
|
+
correctly.
|
|
197
|
+
"""
|
|
198
|
+
return AnimationLibrary(
|
|
199
|
+
animations=library.animations,
|
|
200
|
+
spritesheet_path=library.spritesheet_path,
|
|
201
|
+
tile_size=(int(library.tile_size[0] * scale), int(library.tile_size[1] * scale)),
|
|
202
|
+
grid_offset=(int(library.grid_offset[0] * scale), int(library.grid_offset[1] * scale)),
|
|
203
|
+
trim_transparent=library.trim_transparent,
|
|
204
|
+
)
|
|
@@ -8,7 +8,7 @@ repeated file I/O during runtime.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import Dict, Optional, Union
|
|
11
|
+
from typing import Dict, Optional, Tuple, Union
|
|
12
12
|
|
|
13
13
|
from ..parser.collision import (
|
|
14
14
|
CharacterCollision,
|
|
@@ -39,7 +39,7 @@ class CollisionCache:
|
|
|
39
39
|
|
|
40
40
|
def __init__(self):
|
|
41
41
|
self._tileset_cache: Dict[str, Optional[TilesetCollision]] = {}
|
|
42
|
-
self._character_cache: Dict[str, Optional[CharacterCollision]] = {}
|
|
42
|
+
self._character_cache: Dict[Tuple[str, float], Optional[CharacterCollision]] = {}
|
|
43
43
|
self._object_cache: Dict[str, Optional[ObjectCollisionData]] = {}
|
|
44
44
|
|
|
45
45
|
def get_tileset_collision(
|
|
@@ -58,17 +58,21 @@ class CollisionCache:
|
|
|
58
58
|
return self._tileset_cache[key]
|
|
59
59
|
|
|
60
60
|
def get_character_collision(
|
|
61
|
-
self, collision_path: Union[str, Path]
|
|
61
|
+
self, collision_path: Union[str, Path], render_scale: float = 1.0
|
|
62
62
|
) -> Optional[CharacterCollision]:
|
|
63
63
|
"""Get character collision data (cached).
|
|
64
64
|
|
|
65
65
|
Args:
|
|
66
66
|
collision_path: Direct path to the .collision.json file.
|
|
67
|
+
render_scale: Multiplier applied to shape dimensions and offsets.
|
|
68
|
+
Cached per (path, render_scale) pair.
|
|
67
69
|
"""
|
|
68
|
-
key = str(Path(collision_path).resolve())
|
|
70
|
+
key = (str(Path(collision_path).resolve()), render_scale)
|
|
69
71
|
|
|
70
72
|
if key not in self._character_cache:
|
|
71
|
-
self._character_cache[key] = load_character_collision(
|
|
73
|
+
self._character_cache[key] = load_character_collision(
|
|
74
|
+
collision_path, render_scale=render_scale
|
|
75
|
+
)
|
|
72
76
|
|
|
73
77
|
return self._character_cache[key]
|
|
74
78
|
|
|
@@ -134,13 +138,15 @@ def get_cached_tileset_collision(
|
|
|
134
138
|
|
|
135
139
|
def get_cached_character_collision(
|
|
136
140
|
collision_path: Union[str, Path],
|
|
141
|
+
render_scale: float = 1.0,
|
|
137
142
|
) -> Optional[CharacterCollision]:
|
|
138
143
|
"""Get character collision using global cache.
|
|
139
144
|
|
|
140
145
|
Args:
|
|
141
146
|
collision_path: Direct path to the .collision.json file.
|
|
147
|
+
render_scale: Multiplier applied to shape dimensions and offsets.
|
|
142
148
|
"""
|
|
143
|
-
return _global_cache.get_character_collision(collision_path)
|
|
149
|
+
return _global_cache.get_character_collision(collision_path, render_scale=render_scale)
|
|
144
150
|
|
|
145
151
|
|
|
146
152
|
def get_cached_object_collision(
|
|
@@ -177,6 +177,23 @@ def _make_heart_texture() -> Surface:
|
|
|
177
177
|
return s
|
|
178
178
|
|
|
179
179
|
|
|
180
|
+
def _make_line_texture() -> Surface:
|
|
181
|
+
# will be applied on next release not yet
|
|
182
|
+
"""Vertical streak spanning the full canvas with a thin core.
|
|
183
|
+
|
|
184
|
+
Kept axis-aligned (not rotated): the renderer only rotates particles
|
|
185
|
+
when ``rotation_speed`` is non-zero, so a vertical line reads as a
|
|
186
|
+
falling streak for rain and stream effects.
|
|
187
|
+
"""
|
|
188
|
+
s = Surface((PARTICLE_TEXTURE_SIZE, PARTICLE_TEXTURE_SIZE), pygame.SRCALPHA)
|
|
189
|
+
w = max(1, PARTICLE_TEXTURE_SIZE // 6)
|
|
190
|
+
x = (PARTICLE_TEXTURE_SIZE - w) // 2
|
|
191
|
+
pygame.draw.rect(s, (255, 255, 255, 255), Rect(x, 2, w, PARTICLE_TEXTURE_SIZE - 4))
|
|
192
|
+
pygame.draw.rect(s, (255, 255, 255, 140), Rect(x, 0, w, 2))
|
|
193
|
+
pygame.draw.rect(s, (255, 255, 255, 140), Rect(x, PARTICLE_TEXTURE_SIZE - 2, w, 2))
|
|
194
|
+
return s
|
|
195
|
+
|
|
196
|
+
|
|
180
197
|
def _get_base_texture(shape: str) -> Surface:
|
|
181
198
|
if shape not in _TEXTURE_CACHE:
|
|
182
199
|
makers = {
|
|
@@ -188,6 +205,7 @@ def _get_base_texture(shape: str) -> Surface:
|
|
|
188
205
|
"smoke": _make_smoke_texture,
|
|
189
206
|
"fog": _make_fog_texture,
|
|
190
207
|
"heart": _make_heart_texture,
|
|
208
|
+
"line": _make_line_texture,
|
|
191
209
|
}
|
|
192
210
|
maker = makers.get(shape, _make_circle_texture)
|
|
193
211
|
_TEXTURE_CACHE[shape] = maker()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tilemap-parser
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.4
|
|
4
4
|
Summary: Standalone parser/loader for tilemap-editor JSON maps, sprite animations, and collision detection runtime.
|
|
5
5
|
Author: tilemap parser contributors
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -192,6 +192,60 @@ class TestParseCharacterCollision:
|
|
|
192
192
|
result = parse_character_collision(data)
|
|
193
193
|
assert result.properties == {}
|
|
194
194
|
|
|
195
|
+
def test_render_scale_rectangle(self):
|
|
196
|
+
result = parse_character_collision(CHARACTER_RECT_DATA, render_scale=2.0)
|
|
197
|
+
assert isinstance(result.shape, RectangleShape)
|
|
198
|
+
assert result.shape.width == 48.0
|
|
199
|
+
assert result.shape.height == 64.0
|
|
200
|
+
assert result.shape.offset == (8.0, 0.0)
|
|
201
|
+
|
|
202
|
+
def test_render_scale_circle(self):
|
|
203
|
+
result = parse_character_collision(CHARACTER_CIRCLE_DATA, render_scale=3.0)
|
|
204
|
+
assert isinstance(result.shape, CircleShape)
|
|
205
|
+
assert result.shape.radius == 48.0
|
|
206
|
+
|
|
207
|
+
def test_render_scale_capsule(self):
|
|
208
|
+
result = parse_character_collision(CHARACTER_CAPSULE_DATA, render_scale=2.0)
|
|
209
|
+
assert isinstance(result.shape, CapsuleShape)
|
|
210
|
+
assert result.shape.radius == 16.0
|
|
211
|
+
assert result.shape.height == 96.0
|
|
212
|
+
|
|
213
|
+
def test_render_scale_polygon(self):
|
|
214
|
+
data = {
|
|
215
|
+
"name": "poly",
|
|
216
|
+
"shape": {
|
|
217
|
+
"type": "polygon",
|
|
218
|
+
"vertices": [[0, 0], [10, 0], [10, 10], [0, 10]],
|
|
219
|
+
},
|
|
220
|
+
"properties": {},
|
|
221
|
+
}
|
|
222
|
+
result = parse_character_collision(data, render_scale=2.0)
|
|
223
|
+
assert isinstance(result.shape, CollisionPolygon)
|
|
224
|
+
assert result.shape.vertices == [(0.0, 0.0), (20.0, 0.0), (20.0, 20.0), (0.0, 20.0)]
|
|
225
|
+
|
|
226
|
+
def test_render_scale_default_is_one(self):
|
|
227
|
+
result = parse_character_collision(CHARACTER_RECT_DATA)
|
|
228
|
+
assert isinstance(result.shape, RectangleShape)
|
|
229
|
+
assert result.shape.width == 24.0
|
|
230
|
+
|
|
231
|
+
def test_render_scale_zero_rejected(self):
|
|
232
|
+
with pytest.raises(CollisionParseError, match="render_scale"):
|
|
233
|
+
parse_character_collision(CHARACTER_RECT_DATA, render_scale=0)
|
|
234
|
+
|
|
235
|
+
def test_render_scale_negative_rejected(self):
|
|
236
|
+
with pytest.raises(CollisionParseError, match="render_scale"):
|
|
237
|
+
parse_character_collision(CHARACTER_RECT_DATA, render_scale=-1)
|
|
238
|
+
|
|
239
|
+
def test_render_scale_nonfinite_rejected(self):
|
|
240
|
+
for bad in (float("nan"), float("inf"), float("-inf")):
|
|
241
|
+
with pytest.raises(CollisionParseError, match="render_scale"):
|
|
242
|
+
parse_character_collision(CHARACTER_RECT_DATA, render_scale=bad)
|
|
243
|
+
|
|
244
|
+
def test_render_scale_non_numeric_rejected(self):
|
|
245
|
+
for bad in (None, "2", "abc"):
|
|
246
|
+
with pytest.raises(CollisionParseError, match="render_scale"):
|
|
247
|
+
parse_character_collision(CHARACTER_RECT_DATA, render_scale=bad)
|
|
248
|
+
|
|
195
249
|
|
|
196
250
|
# ===========================================================================
|
|
197
251
|
# Shape helpers
|
|
@@ -232,6 +286,54 @@ class TestShapeHelpers:
|
|
|
232
286
|
assert transformed.vertices[1] == (15.0, 7.0)
|
|
233
287
|
assert transformed.vertices[2] == (15.0, 17.0)
|
|
234
288
|
|
|
289
|
+
def test_rectangle_scaled(self):
|
|
290
|
+
shape = RectangleShape(width=24.0, height=32.0, offset=(4.0, 0.0))
|
|
291
|
+
scaled = shape.scaled(2.0)
|
|
292
|
+
assert isinstance(scaled, RectangleShape)
|
|
293
|
+
assert scaled.width == 48.0
|
|
294
|
+
assert scaled.height == 64.0
|
|
295
|
+
assert scaled.offset == (8.0, 0.0)
|
|
296
|
+
assert shape.width == 24.0 # original untouched
|
|
297
|
+
|
|
298
|
+
def test_circle_scaled(self):
|
|
299
|
+
shape = CircleShape(radius=16.0, offset=(2.0, 3.0))
|
|
300
|
+
scaled = shape.scaled(3.0)
|
|
301
|
+
assert isinstance(scaled, CircleShape)
|
|
302
|
+
assert scaled.radius == 48.0
|
|
303
|
+
assert scaled.offset == (6.0, 9.0)
|
|
304
|
+
|
|
305
|
+
def test_capsule_scaled(self):
|
|
306
|
+
shape = CapsuleShape(radius=8.0, height=48.0, offset=(0.0, 2.0))
|
|
307
|
+
scaled = shape.scaled(1.5)
|
|
308
|
+
assert isinstance(scaled, CapsuleShape)
|
|
309
|
+
assert scaled.radius == 12.0
|
|
310
|
+
assert scaled.height == 72.0
|
|
311
|
+
assert scaled.offset == (0.0, 3.0)
|
|
312
|
+
|
|
313
|
+
def test_collision_polygon_scaled(self):
|
|
314
|
+
poly = CollisionPolygon(vertices=[(0.0, 0.0), (10.0, 0.0), (10.0, 10.0)], one_way=True)
|
|
315
|
+
scaled = poly.scaled(2.0)
|
|
316
|
+
assert isinstance(scaled, CollisionPolygon)
|
|
317
|
+
assert scaled.vertices == [(0.0, 0.0), (20.0, 0.0), (20.0, 20.0)]
|
|
318
|
+
assert scaled.one_way is True
|
|
319
|
+
|
|
320
|
+
def test_scaled_one_is_identity(self):
|
|
321
|
+
shape = RectangleShape(width=24.0, height=32.0, offset=(4.0, 0.0))
|
|
322
|
+
assert shape.scaled(1.0) == shape
|
|
323
|
+
|
|
324
|
+
def test_character_collision_scaled(self):
|
|
325
|
+
coll = parse_character_collision(CHARACTER_RECT_DATA)
|
|
326
|
+
scaled = coll.scaled(2.0)
|
|
327
|
+
assert scaled.name == coll.name
|
|
328
|
+
assert isinstance(scaled.shape, RectangleShape)
|
|
329
|
+
assert scaled.shape.width == 48.0
|
|
330
|
+
assert scaled.shape.height == 64.0
|
|
331
|
+
assert scaled.shape.offset == (8.0, 0.0)
|
|
332
|
+
assert scaled.collision_layer == coll.collision_layer
|
|
333
|
+
assert scaled.collision_mask == coll.collision_mask
|
|
334
|
+
assert scaled.properties == coll.properties
|
|
335
|
+
assert coll.shape.width == 24.0 # original untouched
|
|
336
|
+
|
|
235
337
|
|
|
236
338
|
# ===========================================================================
|
|
237
339
|
# load_tileset_collision — direct path API
|
|
@@ -308,6 +410,16 @@ class TestLoadCharacterCollision:
|
|
|
308
410
|
result = load_character_collision(CHARACTER_COLLISION)
|
|
309
411
|
assert isinstance(result.shape, RectangleShape)
|
|
310
412
|
|
|
413
|
+
def test_render_scale_scales_shape(self):
|
|
414
|
+
base = load_character_collision(CHARACTER_COLLISION)
|
|
415
|
+
scaled = load_character_collision(CHARACTER_COLLISION, render_scale=2.0)
|
|
416
|
+
assert base is not None and scaled is not None
|
|
417
|
+
assert isinstance(base.shape, RectangleShape)
|
|
418
|
+
assert isinstance(scaled.shape, RectangleShape)
|
|
419
|
+
assert scaled.shape.width == base.shape.width * 2.0
|
|
420
|
+
assert scaled.shape.height == base.shape.height * 2.0
|
|
421
|
+
assert scaled.shape.offset == (base.shape.offset[0] * 2.0, base.shape.offset[1] * 2.0)
|
|
422
|
+
|
|
311
423
|
def test_non_json_file_raises(self, tmp_path):
|
|
312
424
|
# Passing a non-JSON file (e.g. an image) raises CollisionParseError,
|
|
313
425
|
# not silently returns None — the file exists so we try to parse it.
|
|
@@ -357,7 +469,7 @@ class TestCollisionCache:
|
|
|
357
469
|
|
|
358
470
|
def test_preload_character(self):
|
|
359
471
|
self.cache.preload_character(CHARACTER_COLLISION)
|
|
360
|
-
key = str(CHARACTER_COLLISION.resolve())
|
|
472
|
+
key = (str(CHARACTER_COLLISION.resolve()), 1.0)
|
|
361
473
|
assert key in self.cache._character_cache
|
|
362
474
|
|
|
363
475
|
def test_cache_key_is_resolved_path(self, tmp_path):
|
|
@@ -369,6 +481,14 @@ class TestCollisionCache:
|
|
|
369
481
|
r2 = self.cache.get_tileset_collision(link)
|
|
370
482
|
assert r1 is r2
|
|
371
483
|
|
|
484
|
+
def test_character_cache_separates_render_scales(self):
|
|
485
|
+
r1 = self.cache.get_character_collision(CHARACTER_COLLISION)
|
|
486
|
+
r2 = self.cache.get_character_collision(CHARACTER_COLLISION, render_scale=1.0)
|
|
487
|
+
r3 = self.cache.get_character_collision(CHARACTER_COLLISION, render_scale=2.0)
|
|
488
|
+
assert r1 is r2 # same scale -> same cached object
|
|
489
|
+
assert r1 is not r3 # different scale -> separate cache entry
|
|
490
|
+
assert r3.shape.width == r1.shape.width * 2.0
|
|
491
|
+
|
|
372
492
|
|
|
373
493
|
# ===========================================================================
|
|
374
494
|
# Global cache helpers
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import pygame
|
|
3
6
|
import pytest
|
|
4
7
|
|
|
5
8
|
from tilemap_parser import (
|
|
@@ -115,6 +118,128 @@ class TestSpriteAnimationSetLoad:
|
|
|
115
118
|
bounds = anim_set.get_content_bounds("nonexistent")
|
|
116
119
|
assert bounds is None
|
|
117
120
|
|
|
121
|
+
def test_load_with_render_scale(self):
|
|
122
|
+
anim_set = SpriteAnimationSet.load(PLAYER_ANIM_PATH, render_scale=2.0)
|
|
123
|
+
assert anim_set.render_scale == 2.0
|
|
124
|
+
assert anim_set.surface.get_width() == 2400
|
|
125
|
+
assert anim_set.surface.get_height() == 840
|
|
126
|
+
assert anim_set.library.tile_size == (300, 120)
|
|
127
|
+
assert anim_set.library.grid_offset == (0, 0)
|
|
128
|
+
assert anim_set.grid_offset_x == 0
|
|
129
|
+
|
|
130
|
+
def test_render_scale_one_matches_default(self):
|
|
131
|
+
default = SpriteAnimationSet.load(PLAYER_ANIM_PATH)
|
|
132
|
+
explicit = SpriteAnimationSet.load(PLAYER_ANIM_PATH, render_scale=1.0)
|
|
133
|
+
assert explicit.render_scale == 1.0
|
|
134
|
+
assert explicit.surface.get_size() == default.surface.get_size()
|
|
135
|
+
assert explicit.library.tile_size == default.library.tile_size
|
|
136
|
+
|
|
137
|
+
def test_get_image_scaled_by_render_scale(self):
|
|
138
|
+
anim_set = SpriteAnimationSet.load(PLAYER_ANIM_PATH, render_scale=2.0)
|
|
139
|
+
img = anim_set.get_image(0)
|
|
140
|
+
assert img is not None
|
|
141
|
+
assert img.get_width() == 300
|
|
142
|
+
assert img.get_height() == 120
|
|
143
|
+
|
|
144
|
+
def test_get_image_cell_lookup_still_valid_at_scale(self):
|
|
145
|
+
anim_set = SpriteAnimationSet.load(PLAYER_ANIM_PATH, render_scale=2.0)
|
|
146
|
+
img = anim_set.get_image(8)
|
|
147
|
+
assert img is not None
|
|
148
|
+
assert img.get_size() == (300, 120)
|
|
149
|
+
|
|
150
|
+
def test_get_content_bounds_scaled(self):
|
|
151
|
+
base = SpriteAnimationSet.load(PLAYER_ANIM_PATH)
|
|
152
|
+
scaled = SpriteAnimationSet.load(PLAYER_ANIM_PATH, render_scale=3.0)
|
|
153
|
+
base_bounds = base.get_content_bounds("idle")
|
|
154
|
+
scaled_bounds = scaled.get_content_bounds("idle")
|
|
155
|
+
assert base_bounds is not None and scaled_bounds is not None
|
|
156
|
+
assert scaled_bounds.width == base_bounds.width * 3
|
|
157
|
+
assert scaled_bounds.height == base_bounds.height * 3
|
|
158
|
+
assert scaled_bounds.x == base_bounds.x * 3
|
|
159
|
+
assert scaled_bounds.y == base_bounds.y * 3
|
|
160
|
+
|
|
161
|
+
def test_invalid_render_scale_raises(self):
|
|
162
|
+
with pytest.raises(ValueError):
|
|
163
|
+
SpriteAnimationSet.load(PLAYER_ANIM_PATH, render_scale=0)
|
|
164
|
+
|
|
165
|
+
def test_zero_sized_scaled_cells_rejected(self):
|
|
166
|
+
for bad in (0.01, 0.0005):
|
|
167
|
+
with pytest.raises(ValueError, match="zero-sized"):
|
|
168
|
+
SpriteAnimationSet.load(PLAYER_ANIM_PATH, render_scale=bad)
|
|
169
|
+
|
|
170
|
+
def test_animation_player_frames_scaled(self):
|
|
171
|
+
anim_set = SpriteAnimationSet.load(PLAYER_ANIM_PATH, render_scale=2.0)
|
|
172
|
+
player = AnimationPlayer(anim_set, "idle")
|
|
173
|
+
img = player.get_current_image()
|
|
174
|
+
assert img is not None
|
|
175
|
+
assert img.get_size() == (300, 120)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _write_offset_atlas(tmp_path: Path, cols: int, rows: int):
|
|
179
|
+
"""Write a synthetic atlas with a nonzero grid offset and solid per-cell colors."""
|
|
180
|
+
tile = 8
|
|
181
|
+
ox, oy = 1, 1
|
|
182
|
+
w, h = ox + cols * tile, oy + rows * tile
|
|
183
|
+
sheet = pygame.Surface((w, h))
|
|
184
|
+
colors = {}
|
|
185
|
+
for row in range(rows):
|
|
186
|
+
for col in range(cols):
|
|
187
|
+
idx = row * cols + col
|
|
188
|
+
color = (
|
|
189
|
+
(20 + idx * 7) % 256,
|
|
190
|
+
(40 + idx * 13) % 256,
|
|
191
|
+
(60 + idx * 29) % 256,
|
|
192
|
+
)
|
|
193
|
+
colors[idx] = color
|
|
194
|
+
sheet.fill(color, (ox + col * tile, oy + row * tile, tile, tile))
|
|
195
|
+
sheet.fill((250, 250, 250), (0, 0, w, oy))
|
|
196
|
+
sheet.fill((250, 250, 250), (0, 0, ox, h))
|
|
197
|
+
png_path = tmp_path / "atlas.png"
|
|
198
|
+
pygame.image.save(sheet, str(png_path))
|
|
199
|
+
json_path = tmp_path / "atlas.animation.json"
|
|
200
|
+
json_path.write_text(
|
|
201
|
+
json.dumps(
|
|
202
|
+
{
|
|
203
|
+
"spritesheet_path": str(png_path),
|
|
204
|
+
"tile_size": [tile, tile],
|
|
205
|
+
"grid_offset": [ox, oy],
|
|
206
|
+
"trim_transparent": False,
|
|
207
|
+
"animations": {
|
|
208
|
+
"test": {
|
|
209
|
+
"name": "test",
|
|
210
|
+
"frames": [{"variant_id": 0, "duration_ms": 100.0}],
|
|
211
|
+
"loop": True,
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
return colors
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class TestFractionalScaleWithOffset:
|
|
221
|
+
def test_downscale_0_6_returns_correct_cell(self, tmp_path):
|
|
222
|
+
colors = _write_offset_atlas(tmp_path, cols=5, rows=3)
|
|
223
|
+
anim_set = SpriteAnimationSet.load(
|
|
224
|
+
tmp_path / "atlas.animation.json", render_scale=0.6
|
|
225
|
+
)
|
|
226
|
+
for variant in (0, 5, 6):
|
|
227
|
+
img = anim_set.get_image(variant)
|
|
228
|
+
assert img is not None
|
|
229
|
+
center = img.get_at((img.get_width() // 2, img.get_height() // 2))[:3]
|
|
230
|
+
assert center == colors[variant]
|
|
231
|
+
|
|
232
|
+
def test_upscale_1_1_returns_correct_cell(self, tmp_path):
|
|
233
|
+
colors = _write_offset_atlas(tmp_path, cols=12, rows=3)
|
|
234
|
+
anim_set = SpriteAnimationSet.load(
|
|
235
|
+
tmp_path / "atlas.animation.json", render_scale=1.1
|
|
236
|
+
)
|
|
237
|
+
for variant in (0, 12, 13):
|
|
238
|
+
img = anim_set.get_image(variant)
|
|
239
|
+
assert img is not None
|
|
240
|
+
center = img.get_at((img.get_width() // 2, img.get_height() // 2))[:3]
|
|
241
|
+
assert center == colors[variant]
|
|
242
|
+
|
|
118
243
|
|
|
119
244
|
class TestAnimationPlayerPlayback:
|
|
120
245
|
def test_initial_state(self):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/collision/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/collision/manager.py
RENAMED
|
File without changes
|
{tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/collision/shapes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/movement/__init__.py
RENAMED
|
File without changes
|
{tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/movement/grounded.py
RENAMED
|
File without changes
|
{tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/movement/platformer.py
RENAMED
|
File without changes
|
{tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/movement/queries.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/navigation/__init__.py
RENAMED
|
File without changes
|
{tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/navigation/nav_grid.py
RENAMED
|
File without changes
|
{tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/navigation/path_follower.py
RENAMED
|
File without changes
|
{tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/navigation/pathfinder.py
RENAMED
|
File without changes
|
{tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser/runtime/object_collision.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tilemap_parser-5.0.3 → tilemap_parser-5.0.4}/src/tilemap_parser.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|