tilemap-parser 4.2.4__tar.gz → 4.2.6__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.
Files changed (50) hide show
  1. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/PKG-INFO +1 -1
  2. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/pyproject.toml +1 -1
  3. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/runtime/area_node.py +9 -2
  4. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/runtime/map_loader.py +24 -8
  5. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/runtime/map_object.py +41 -10
  6. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/runtime/renderer.py +2 -0
  7. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser.egg-info/PKG-INFO +1 -1
  8. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_map_loader.py +99 -0
  9. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_map_object.py +22 -0
  10. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/LICENSE +0 -0
  11. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/README.md +0 -0
  12. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/setup.cfg +0 -0
  13. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/__init__.py +0 -0
  14. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/parser/__init__.py +0 -0
  15. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/parser/animation.py +0 -0
  16. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/parser/collision.py +0 -0
  17. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/parser/collision_loader.py +0 -0
  18. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/parser/map_parse.py +0 -0
  19. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/parser/node_parse.py +0 -0
  20. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/parser/particle.py +0 -0
  21. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/parser/tmx_converter.py +0 -0
  22. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/runtime/__init__.py +0 -0
  23. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/runtime/animation_player.py +0 -0
  24. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/runtime/camera.py +0 -0
  25. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/runtime/collision_cache.py +0 -0
  26. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/runtime/object_collision.py +0 -0
  27. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/runtime/particles.py +0 -0
  28. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/runtime/protocols.py +0 -0
  29. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/runtime/tile_collision.py +0 -0
  30. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/utils/__init__.py +0 -0
  31. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser/utils/geometry.py +0 -0
  32. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser.egg-info/SOURCES.txt +0 -0
  33. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser.egg-info/dependency_links.txt +0 -0
  34. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser.egg-info/requires.txt +0 -0
  35. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/src/tilemap_parser.egg-info/top_level.txt +0 -0
  36. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_camera.py +0 -0
  37. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_collision.py +0 -0
  38. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_geometry.py +0 -0
  39. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_gid_collision.py +0 -0
  40. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_integration_animation.py +0 -0
  41. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_integration_collision.py +0 -0
  42. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_integration_map_loader.py +0 -0
  43. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_move_grounded.py +0 -0
  44. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_object_collision.py +0 -0
  45. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_object_surfaces.py +0 -0
  46. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_particle_emitter.py +0 -0
  47. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_render_scale.py +0 -0
  48. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_tile_collision.py +0 -0
  49. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_tile_layer_renderer_y_sort.py +0 -0
  50. {tilemap_parser-4.2.4 → tilemap_parser-4.2.6}/tests/test_tmx_converter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-parser
3
- Version: 4.2.4
3
+ Version: 4.2.6
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 = "4.2.4"
7
+ version = "4.2.6"
8
8
  description = "Standalone parser/loader for tilemap-editor JSON maps, sprite animations, and collision detection runtime."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -8,11 +8,18 @@ from ..parser.node_parse import ParsedNode
8
8
 
9
9
 
10
10
  class AreaNode:
11
- def __init__(self, parsed: ParsedNode) -> None:
11
+ def __init__(self, parsed: ParsedNode, render_scale: float = 1.0) -> None:
12
12
  self.node_id = parsed.node_id
13
13
  self.name = parsed.name
14
14
  self.node_type = parsed.node_type
15
- self._rect = Rect(parsed.area.x, parsed.area.y, parsed.area.w, parsed.area.h)
15
+ self.render_scale = render_scale
16
+ rs = render_scale
17
+ self._rect = Rect(
18
+ int(parsed.area.x * rs),
19
+ int(parsed.area.y * rs),
20
+ int(parsed.area.w * rs),
21
+ int(parsed.area.h * rs),
22
+ )
16
23
  self.layer_name = parsed.layer_name
17
24
  self.properties = dict(parsed.properties)
18
25
  self.group = parsed.group
@@ -109,7 +109,11 @@ class TilemapData:
109
109
  origin_offset = _normalize_origin(parsed)
110
110
  result = cls(parsed, surfaces, resolved_paths, warnings, map_path=p)
111
111
  result.origin_offset = origin_offset
112
- result.area_nodes = [AreaNode(n) for n in parsed.nodes if n.node_type == "area"]
112
+ result.area_nodes = [
113
+ AreaNode(n, render_scale=result.render_scale)
114
+ for n in parsed.nodes
115
+ if n.node_type == "area"
116
+ ]
113
117
  result.particle_emitters = [
114
118
  ParticleEmitterNode(n) for n in parsed.nodes if n.node_type == "particle_emitter"
115
119
  ]
@@ -275,8 +279,8 @@ class TilemapData:
275
279
  return self.get_tile_surface(tile.ttype, tile.variant)
276
280
 
277
281
  def get_object_surface_by_id(
278
- self, layer_id_or_name: Union[int, str], object_id: int, *, copy_surface: bool = True
279
- ) -> Optional[Tuple[Surface, int, int]]:
282
+ self, layer_id_or_name: Union[int, str], object_id: int, *, copy_surface: bool = True, scaled: bool = False
283
+ ) -> Optional[Tuple[Surface, float, float]]:
280
284
  layer = self.get_layer(layer_id_or_name)
281
285
  if layer is None or layer.layer_type != "object":
282
286
  return None
@@ -286,19 +290,31 @@ class TilemapData:
286
290
  surf = self.get_object_surface(obj, copy_surface=copy_surface)
287
291
  if surf is None:
288
292
  return None
289
- return surf, obj.area.x, obj.area.y
293
+ rs = self.render_scale if scaled else 1.0
294
+ x = obj.area.x * rs
295
+ y = obj.area.y * rs
296
+ if scaled and rs != 1.0:
297
+ w, h = surf.get_size()
298
+ surf = pygame.transform.scale(surf, (int(w * rs), int(h * rs)))
299
+ return surf, x, y
290
300
 
291
301
  def get_object_surfaces(
292
- self, layer_id_or_name: Union[int, str], *, copy_surface: bool = True
293
- ) -> List[Tuple[Surface, int, int, int]]:
302
+ self, layer_id_or_name: Union[int, str], *, copy_surface: bool = True, scaled: bool = False
303
+ ) -> List[Tuple[Surface, float, float, int]]:
294
304
  layer = self.get_layer(layer_id_or_name)
295
305
  if layer is None or layer.layer_type != "object":
296
306
  return []
297
- result: List[Tuple[Surface, int, int, int]] = []
307
+ result: List[Tuple[Surface, float, float, int]] = []
308
+ rs = self.render_scale if scaled else 1.0
298
309
  for oid, obj in layer.objects.items():
299
310
  surf = self.get_object_surface(obj, copy_surface=copy_surface)
300
311
  if surf is not None:
301
- result.append((surf, obj.area.x, obj.area.y, oid))
312
+ x = obj.area.x * rs
313
+ y = obj.area.y * rs
314
+ if scaled and rs != 1.0:
315
+ w, h = surf.get_size()
316
+ surf = pygame.transform.scale(surf, (int(w * rs), int(h * rs)))
317
+ result.append((surf, x, y, oid))
302
318
  return result
303
319
 
304
320
  def get_tileset_animation(self, ttype: int) -> Optional[dict]:
@@ -63,8 +63,8 @@ class MapObject:
63
63
  self,
64
64
  x: float,
65
65
  y: float,
66
- surface: Surface,
67
- collision_shape: CollisionPolygon,
66
+ surface: Optional[Surface] = None,
67
+ collision_shape: Optional[CollisionPolygon] = None,
68
68
  *,
69
69
  collision_shapes: Optional[List[CollisionPolygon]] = None,
70
70
  collision_layer: int = 1,
@@ -74,12 +74,23 @@ class MapObject:
74
74
  self.x = x
75
75
  self.y = y
76
76
  self.surface = surface
77
- self.collision_shapes = collision_shapes or [collision_shape]
78
- self.collision_shape = self.collision_shapes[0]
77
+ if collision_shapes is not None:
78
+ self.collision_shapes = collision_shapes
79
+ self.collision_shape = collision_shapes[0] if collision_shapes else None
80
+ elif collision_shape is not None:
81
+ self.collision_shapes = [collision_shape]
82
+ self.collision_shape = collision_shape
83
+ else:
84
+ self.collision_shapes = []
85
+ self.collision_shape = None
79
86
  self.collision_layer = collision_layer
80
87
  self.collision_mask = collision_mask
81
88
  self.y_sort_origin = y_sort_origin
82
89
 
90
+ @property
91
+ def has_collision(self) -> bool:
92
+ return len(self.collision_shapes) > 0
93
+
83
94
 
84
95
  def _resolve_object_collision_filename(tileset_path: Union[str, Path]) -> str:
85
96
  """Extract the object collision filename for a given tileset path.
@@ -94,8 +105,9 @@ def load_map_objects(
94
105
  collision_dir: Union[str, Path],
95
106
  *,
96
107
  cache: Optional[CollisionCache] = None,
108
+ require_collision: bool = True,
97
109
  ) -> List[MapObject]:
98
- """Load all collidable objects from a tilemap.
110
+ """Load objects from a tilemap.
99
111
 
100
112
  Iterates every object layer in *tilemap_data*, resolves the
101
113
  corresponding ``.object_collision.json`` from *collision_dir*, and
@@ -129,13 +141,12 @@ def load_map_objects(
129
141
  files (typically ``<map_path>/collision/``).
130
142
  cache: Optional :class:`CollisionCache` for caching parsed
131
143
  collision data across calls.
144
+ require_collision: If True (default), only objects with matching
145
+ collision regions are returned. If False, visual-only objects
146
+ without collision are also included.
132
147
 
133
148
  Returns:
134
- List of :class:`MapObject` instances, one per map object that
135
- has both a valid surface and a matching collision region.
136
- When a region contains multiple collision polygons, all shapes
137
- are preserved in :attr:`MapObject.collision_shapes` and the
138
- collision system checks each shape pair during narrowphase.
149
+ List of :class:`MapObject` instances.
139
150
  """
140
151
  collision_dir = Path(collision_dir)
141
152
  objects: List[MapObject] = []
@@ -167,6 +178,16 @@ def load_map_objects(
167
178
 
168
179
  collision_data = loaded_collision[ttype]
169
180
  if collision_data is None:
181
+ if require_collision:
182
+ continue
183
+ game_obj = MapObject(
184
+ x=x,
185
+ y=y,
186
+ surface=surf,
187
+ collision_shape=None,
188
+ collision_shapes=[],
189
+ )
190
+ objects.append(game_obj)
170
191
  continue
171
192
 
172
193
  world_shapes = []
@@ -180,6 +201,16 @@ def load_map_objects(
180
201
  oy = region.region_rect[1] * rs
181
202
  world_shapes.extend(shape.transform(ox, oy, rs) for shape in region.shapes)
182
203
  if not world_shapes:
204
+ if require_collision:
205
+ continue
206
+ game_obj = MapObject(
207
+ x=x,
208
+ y=y,
209
+ surface=surf,
210
+ collision_shape=None,
211
+ collision_shapes=[],
212
+ )
213
+ objects.append(game_obj)
183
214
  continue
184
215
 
185
216
  game_obj = MapObject(
@@ -178,6 +178,8 @@ class TileLayerRenderer:
178
178
  tile_iter = sorted(chunk, key=lambda p: p[1] * self._eff_h + origin)
179
179
 
180
180
  for x, y in tile_iter:
181
+ if not (min_x <= x <= max_x and min_y <= y <= max_y):
182
+ continue
181
183
  tile = layer.tiles[(x, y)]
182
184
  display_variant = self._compute_display_variant(tile.variant, tile.ttype, x, y, time_ms)
183
185
  cell = self._get_cached_variant(tile.ttype, display_variant)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-parser
3
- Version: 4.2.4
3
+ Version: 4.2.6
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
@@ -346,3 +346,102 @@ class TestNodeLoading:
346
346
  assert pe.config.lifetime_min == 0.5
347
347
  assert pe.config.lifetime_max == 2.0
348
348
  assert pe.config.gravity_y == 30.0
349
+
350
+ def test_get_object_surfaces_scaled(self, map_data):
351
+ _, data_dir, _, _ = map_data
352
+ # Create map with render_scale = 2.0 and object layer
353
+ map_path = data_dir / "test_map_obj.json"
354
+ payload = {
355
+ "meta": {
356
+ "tile_size": "16;16",
357
+ "map_size": "10;10",
358
+ "version": "1.1",
359
+ "render_scale": 2.0,
360
+ },
361
+ "resources": {"tilesets": [{"path": "../assets/tileset.png", "type": "object"}]},
362
+ "project_state": {"rules": [], "groups": []},
363
+ "data": {
364
+ "ongrid": {},
365
+ "layers": [
366
+ {
367
+ "name": "Obj",
368
+ "type": "object",
369
+ "visible": True,
370
+ "locked": False,
371
+ "opacity": 1.0,
372
+ "z_index": 0,
373
+ "properties": {},
374
+ "tiles": {},
375
+ "objects": {
376
+ "1": {
377
+ "area": {"x": 32, "y": 64, "w": 16, "h": 16},
378
+ "ttype": 0,
379
+ "tileset_type": "object",
380
+ "variant": 0,
381
+ "properties": {},
382
+ }
383
+ },
384
+ }
385
+ ],
386
+ },
387
+ }
388
+ with open(map_path, "w") as f:
389
+ json.dump(payload, f, indent=2)
390
+
391
+ td = TilemapData.load(map_path)
392
+ # Unscaled
393
+ unscaled = td.get_object_surfaces("Obj", scaled=False)
394
+ assert len(unscaled) == 1
395
+ surf_u, x_u, y_u, _ = unscaled[0]
396
+ assert (x_u, y_u) == (32, 64)
397
+ assert surf_u.get_size() == (16, 16)
398
+
399
+ # Scaled
400
+ scaled = td.get_object_surfaces("Obj", scaled=True)
401
+ assert len(scaled) == 1
402
+ surf_s, x_s, y_s, _ = scaled[0]
403
+ assert (x_s, y_s) == (64, 128)
404
+ assert surf_s.get_size() == (32, 32)
405
+
406
+ def test_area_node_scaled(self, map_data):
407
+ _, data_dir, _, _ = map_data
408
+ map_path = data_dir / "test_map_scaled_nodes.json"
409
+ payload = {
410
+ "meta": {
411
+ "tile_size": "16;16",
412
+ "map_size": "10;10",
413
+ "version": "1.1",
414
+ "render_scale": 3.0,
415
+ },
416
+ "resources": {"tilesets": [{"path": "../assets/tileset.png", "type": "tile"}]},
417
+ "project_state": {"rules": [], "groups": []},
418
+ "data": {"ongrid": {}, "layers": []},
419
+ }
420
+ with open(map_path, "w") as f:
421
+ json.dump(payload, f, indent=2)
422
+
423
+ nodes_path = data_dir / "test_map_scaled_nodes.nodes.json"
424
+ nodes_payload = {
425
+ "nodes": [
426
+ {
427
+ "node_id": "area_1",
428
+ "name": "Zone",
429
+ "node_type": "area",
430
+ "area": {"x": 10, "y": 20, "w": 30, "h": 40},
431
+ "layer_name": "main",
432
+ "properties": {},
433
+ "group": "",
434
+ }
435
+ ],
436
+ "groups": [],
437
+ }
438
+ with open(nodes_path, "w") as f:
439
+ json.dump(nodes_payload, f, indent=2)
440
+
441
+ td = TilemapData.load(map_path)
442
+ assert len(td.area_nodes) == 1
443
+ an = td.area_nodes[0]
444
+ assert an.rect == pygame.Rect(30, 60, 90, 120)
445
+
446
+
447
+
@@ -1299,4 +1299,26 @@ class TestLoadMapObjects:
1299
1299
  objects = load_map_objects(td, collision_dir)
1300
1300
  assert objects == []
1301
1301
 
1302
+ def test_load_visual_only_objects(self, map_no_collision_file):
1303
+ td, collision_dir = map_no_collision_file
1304
+ # With require_collision=False, objects without collision files are returned
1305
+ objects = load_map_objects(td, collision_dir, require_collision=False)
1306
+ assert len(objects) == 1
1307
+ obj = objects[0]
1308
+ assert obj.has_collision is False
1309
+ assert obj.collision_shape is None
1310
+ assert obj.collision_shapes == []
1311
+ assert isinstance(obj.surface, pygame.Surface)
1312
+
1313
+ def test_map_object_optional_collision(self):
1314
+ surf = pygame.Surface((16, 16))
1315
+ obj = MapObject(x=10, y=20, surface=surf)
1316
+ assert obj.x == 10
1317
+ assert obj.y == 20
1318
+ assert obj.surface is surf
1319
+ assert obj.collision_shape is None
1320
+ assert obj.collision_shapes == []
1321
+ assert obj.has_collision is False
1322
+
1323
+
1302
1324
 
File without changes
File without changes
File without changes