tilemap-parser 3.1.1__tar.gz → 3.1.3__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-3.1.1 → tilemap_parser-3.1.3}/PKG-INFO +1 -1
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/pyproject.toml +1 -1
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/runtime/tile_collision.py +344 -75
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser.egg-info/PKG-INFO +1 -1
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/LICENSE +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/README.md +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/setup.cfg +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/__init__.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/parser/__init__.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/parser/animation.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/parser/collision.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/parser/collision_loader.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/parser/map_parse.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/runtime/__init__.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/runtime/animation_player.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/runtime/collision_cache.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/runtime/map_loader.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/runtime/object_collision.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/runtime/renderer.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/utils/__init__.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/utils/geometry.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser.egg-info/SOURCES.txt +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser.egg-info/dependency_links.txt +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser.egg-info/requires.txt +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser.egg-info/top_level.txt +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/tests/test_collision.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/tests/test_geometry.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/tests/test_map_loader.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/tests/test_object_collision.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/tests/test_render_scale.py +0 -0
- {tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/tests/test_tile_collision.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tilemap-parser
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.3
|
|
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 = "3.1.
|
|
7
|
+
version = "3.1.3"
|
|
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"
|
|
@@ -14,7 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
import math
|
|
15
15
|
from dataclasses import dataclass
|
|
16
16
|
from enum import Enum
|
|
17
|
-
from typing import List, Optional, Protocol, Tuple, Union
|
|
17
|
+
from typing import Dict, List, Optional, Protocol, Tuple, Union
|
|
18
18
|
|
|
19
19
|
from ..parser.collision import (
|
|
20
20
|
CapsuleShape,
|
|
@@ -71,7 +71,14 @@ def point_in_polygon(point: Point, vertices: List[Point]) -> bool:
|
|
|
71
71
|
return inside
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
def _point_in_polygon_offset(
|
|
74
|
+
def _point_in_polygon_offset(
|
|
75
|
+
px: float,
|
|
76
|
+
py: float,
|
|
77
|
+
vertices: List[Point],
|
|
78
|
+
ox: float,
|
|
79
|
+
oy: float,
|
|
80
|
+
scale: float = 1.0,
|
|
81
|
+
) -> bool:
|
|
75
82
|
"""Ray-cast with tile offset applied inline — no allocation."""
|
|
76
83
|
n = len(vertices)
|
|
77
84
|
inside = False
|
|
@@ -100,18 +107,31 @@ def rect_polygon_collision(
|
|
|
100
107
|
min_vy = max_vy = vertices[0][1]
|
|
101
108
|
for i in range(1, n):
|
|
102
109
|
vx, vy = vertices[i]
|
|
103
|
-
if vx < min_vx:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
110
|
+
if vx < min_vx:
|
|
111
|
+
min_vx = vx
|
|
112
|
+
elif vx > max_vx:
|
|
113
|
+
max_vx = vx
|
|
114
|
+
if vy < min_vy:
|
|
115
|
+
min_vy = vy
|
|
116
|
+
elif vy > max_vy:
|
|
117
|
+
max_vy = vy
|
|
118
|
+
if (
|
|
119
|
+
rect_x > max_vx
|
|
120
|
+
or rect_x + rect_w < min_vx
|
|
121
|
+
or rect_y > max_vy
|
|
122
|
+
or rect_y + rect_h < min_vy
|
|
123
|
+
):
|
|
108
124
|
return False
|
|
109
125
|
|
|
110
126
|
# Corner tests — no tuple allocation
|
|
111
|
-
if point_in_polygon((rect_x,
|
|
112
|
-
|
|
113
|
-
if point_in_polygon((rect_x
|
|
114
|
-
|
|
127
|
+
if point_in_polygon((rect_x, rect_y), vertices):
|
|
128
|
+
return True
|
|
129
|
+
if point_in_polygon((rect_x + rect_w, rect_y), vertices):
|
|
130
|
+
return True
|
|
131
|
+
if point_in_polygon((rect_x, rect_y + rect_h), vertices):
|
|
132
|
+
return True
|
|
133
|
+
if point_in_polygon((rect_x + rect_w, rect_y + rect_h), vertices):
|
|
134
|
+
return True
|
|
115
135
|
|
|
116
136
|
# Vertex-in-rect
|
|
117
137
|
rx2, ry2 = rect_x + rect_w, rect_y + rect_h
|
|
@@ -122,8 +142,14 @@ def rect_polygon_collision(
|
|
|
122
142
|
|
|
123
143
|
|
|
124
144
|
def _rect_polygon_collision_offset(
|
|
125
|
-
rect_x: float,
|
|
126
|
-
|
|
145
|
+
rect_x: float,
|
|
146
|
+
rect_y: float,
|
|
147
|
+
rect_w: float,
|
|
148
|
+
rect_h: float,
|
|
149
|
+
vertices: List[Point],
|
|
150
|
+
ox: float,
|
|
151
|
+
oy: float,
|
|
152
|
+
scale: float = 1.0,
|
|
127
153
|
) -> bool:
|
|
128
154
|
"""Rectangle vs polygon with tile offset applied inline — no allocation."""
|
|
129
155
|
# AABB pre-reject with offset
|
|
@@ -133,19 +159,32 @@ def _rect_polygon_collision_offset(
|
|
|
133
159
|
min_vy = max_vy = v0y
|
|
134
160
|
for i in range(1, n):
|
|
135
161
|
wx, wy = vertices[i][0] * scale + ox, vertices[i][1] * scale + oy
|
|
136
|
-
if wx < min_vx:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
162
|
+
if wx < min_vx:
|
|
163
|
+
min_vx = wx
|
|
164
|
+
elif wx > max_vx:
|
|
165
|
+
max_vx = wx
|
|
166
|
+
if wy < min_vy:
|
|
167
|
+
min_vy = wy
|
|
168
|
+
elif wy > max_vy:
|
|
169
|
+
max_vy = wy
|
|
170
|
+
if (
|
|
171
|
+
rect_x > max_vx
|
|
172
|
+
or rect_x + rect_w < min_vx
|
|
173
|
+
or rect_y > max_vy
|
|
174
|
+
or rect_y + rect_h < min_vy
|
|
175
|
+
):
|
|
141
176
|
return False
|
|
142
177
|
|
|
143
178
|
# Corner tests
|
|
144
179
|
rx2, ry2 = rect_x + rect_w, rect_y + rect_h
|
|
145
|
-
if _point_in_polygon_offset(rect_x, rect_y,
|
|
146
|
-
|
|
147
|
-
if _point_in_polygon_offset(
|
|
148
|
-
|
|
180
|
+
if _point_in_polygon_offset(rect_x, rect_y, vertices, ox, oy, scale):
|
|
181
|
+
return True
|
|
182
|
+
if _point_in_polygon_offset(rx2, rect_y, vertices, ox, oy, scale):
|
|
183
|
+
return True
|
|
184
|
+
if _point_in_polygon_offset(rect_x, ry2, vertices, ox, oy, scale):
|
|
185
|
+
return True
|
|
186
|
+
if _point_in_polygon_offset(rx2, ry2, vertices, ox, oy, scale):
|
|
187
|
+
return True
|
|
149
188
|
|
|
150
189
|
# Vertex-in-rect
|
|
151
190
|
for vx, vy in vertices:
|
|
@@ -184,7 +223,13 @@ def circle_polygon_collision(
|
|
|
184
223
|
|
|
185
224
|
|
|
186
225
|
def _circle_polygon_collision_offset(
|
|
187
|
-
cx: float,
|
|
226
|
+
cx: float,
|
|
227
|
+
cy: float,
|
|
228
|
+
radius: float,
|
|
229
|
+
vertices: List[Point],
|
|
230
|
+
ox: float,
|
|
231
|
+
oy: float,
|
|
232
|
+
scale: float = 1.0,
|
|
188
233
|
) -> bool:
|
|
189
234
|
"""Circle vs polygon with tile offset applied inline — no allocation."""
|
|
190
235
|
if _point_in_polygon_offset(cx, cy, vertices, ox, oy, scale):
|
|
@@ -192,7 +237,10 @@ def _circle_polygon_collision_offset(
|
|
|
192
237
|
n = len(vertices)
|
|
193
238
|
for i in range(n):
|
|
194
239
|
x1, y1 = vertices[i][0] * scale + ox, vertices[i][1] * scale + oy
|
|
195
|
-
x2, y2 =
|
|
240
|
+
x2, y2 = (
|
|
241
|
+
vertices[(i + 1) % n][0] * scale + ox,
|
|
242
|
+
vertices[(i + 1) % n][1] * scale + oy,
|
|
243
|
+
)
|
|
196
244
|
dx = x2 - x1
|
|
197
245
|
dy = y2 - y1
|
|
198
246
|
fx = cx - x1
|
|
@@ -214,7 +262,7 @@ def get_shape_bounds(sprite: ICollidableSprite) -> Tuple[float, float, float, fl
|
|
|
214
262
|
shape = sprite.collision_shape
|
|
215
263
|
if isinstance(shape, RectangleShape):
|
|
216
264
|
left = sprite.x + shape.offset[0]
|
|
217
|
-
top
|
|
265
|
+
top = sprite.y + shape.offset[1]
|
|
218
266
|
return (left, top, left + shape.width, top + shape.height)
|
|
219
267
|
elif isinstance(shape, CircleShape):
|
|
220
268
|
cx, cy = shape.get_center(sprite.x, sprite.y)
|
|
@@ -224,7 +272,12 @@ def get_shape_bounds(sprite: ICollidableSprite) -> Tuple[float, float, float, fl
|
|
|
224
272
|
top_center = shape.get_top_center(sprite.x, sprite.y)
|
|
225
273
|
r = shape.radius
|
|
226
274
|
h = shape.height
|
|
227
|
-
return (
|
|
275
|
+
return (
|
|
276
|
+
top_center[0] - r,
|
|
277
|
+
top_center[1] - r,
|
|
278
|
+
top_center[0] + r,
|
|
279
|
+
top_center[1] + h + r,
|
|
280
|
+
)
|
|
228
281
|
return (sprite.x, sprite.y, sprite.x + 32, sprite.y + 32)
|
|
229
282
|
|
|
230
283
|
|
|
@@ -235,18 +288,26 @@ def check_sprite_polygon_collision(
|
|
|
235
288
|
shape = sprite.collision_shape
|
|
236
289
|
if isinstance(shape, RectangleShape):
|
|
237
290
|
left, top, right, bottom = get_shape_bounds(sprite)
|
|
238
|
-
return rect_polygon_collision(
|
|
291
|
+
return rect_polygon_collision(
|
|
292
|
+
left, top, right - left, bottom - top, polygon.vertices
|
|
293
|
+
)
|
|
239
294
|
elif isinstance(shape, CircleShape):
|
|
240
295
|
center = shape.get_center(sprite.x, sprite.y)
|
|
241
296
|
return circle_polygon_collision(center, shape.radius, polygon.vertices)
|
|
242
297
|
elif isinstance(shape, CapsuleShape):
|
|
243
298
|
left, top, right, bottom = get_shape_bounds(sprite)
|
|
244
|
-
return rect_polygon_collision(
|
|
299
|
+
return rect_polygon_collision(
|
|
300
|
+
left, top, right - left, bottom - top, polygon.vertices
|
|
301
|
+
)
|
|
245
302
|
return False
|
|
246
303
|
|
|
247
304
|
|
|
248
305
|
def _check_sprite_polygon_offset(
|
|
249
|
-
sprite: ICollidableSprite,
|
|
306
|
+
sprite: ICollidableSprite,
|
|
307
|
+
polygon: CollisionPolygon,
|
|
308
|
+
ox: float,
|
|
309
|
+
oy: float,
|
|
310
|
+
scale: float = 1.0,
|
|
250
311
|
) -> bool:
|
|
251
312
|
"""
|
|
252
313
|
Check if sprite collides with a tile-local polygon at world offset (ox, oy).
|
|
@@ -255,18 +316,24 @@ def _check_sprite_polygon_offset(
|
|
|
255
316
|
shape = sprite.collision_shape
|
|
256
317
|
if isinstance(shape, RectangleShape):
|
|
257
318
|
left = sprite.x + shape.offset[0]
|
|
258
|
-
top
|
|
259
|
-
return _rect_polygon_collision_offset(
|
|
319
|
+
top = sprite.y + shape.offset[1]
|
|
320
|
+
return _rect_polygon_collision_offset(
|
|
321
|
+
left, top, shape.width, shape.height, polygon.vertices, ox, oy, scale
|
|
322
|
+
)
|
|
260
323
|
elif isinstance(shape, CircleShape):
|
|
261
324
|
cx = sprite.x + shape.offset[0]
|
|
262
325
|
cy = sprite.y + shape.offset[1]
|
|
263
|
-
return _circle_polygon_collision_offset(
|
|
326
|
+
return _circle_polygon_collision_offset(
|
|
327
|
+
cx, cy, shape.radius, polygon.vertices, ox, oy, scale
|
|
328
|
+
)
|
|
264
329
|
elif isinstance(shape, CapsuleShape):
|
|
265
330
|
left = sprite.x + shape.offset[0] - shape.radius
|
|
266
|
-
top
|
|
267
|
-
w
|
|
268
|
-
h
|
|
269
|
-
return _rect_polygon_collision_offset(
|
|
331
|
+
top = sprite.y + shape.offset[1] - shape.radius
|
|
332
|
+
w = shape.radius * 2
|
|
333
|
+
h = shape.height + shape.radius * 2
|
|
334
|
+
return _rect_polygon_collision_offset(
|
|
335
|
+
left, top, w, h, polygon.vertices, ox, oy, scale
|
|
336
|
+
)
|
|
270
337
|
return False
|
|
271
338
|
|
|
272
339
|
|
|
@@ -329,6 +396,9 @@ class CollisionRunner:
|
|
|
329
396
|
self.max_fall_speed = 600.0
|
|
330
397
|
self.jump_strength = -400.0
|
|
331
398
|
|
|
399
|
+
self.ground_snap_tolerance = 2.0
|
|
400
|
+
self.step_height = 4.0
|
|
401
|
+
|
|
332
402
|
self.slide_friction = 0.1
|
|
333
403
|
|
|
334
404
|
self.rpg_snap_to_grid = False
|
|
@@ -362,7 +432,9 @@ class CollisionRunner:
|
|
|
362
432
|
tile_world_x = tile_x * self._eff_tw
|
|
363
433
|
tile_world_y = tile_y * self._eff_th
|
|
364
434
|
|
|
365
|
-
return tileset_collision.get_world_shapes(
|
|
435
|
+
return tileset_collision.get_world_shapes(
|
|
436
|
+
tile_id, tile_world_x, tile_world_y, self.render_scale
|
|
437
|
+
)
|
|
366
438
|
|
|
367
439
|
def get_nearby_tile_shapes(
|
|
368
440
|
self,
|
|
@@ -381,10 +453,10 @@ class CollisionRunner:
|
|
|
381
453
|
left, top, right, bottom = get_shape_bounds(sprite)
|
|
382
454
|
tw, th = self._eff_tw, self._eff_th
|
|
383
455
|
|
|
384
|
-
min_tile_x = int(left
|
|
456
|
+
min_tile_x = int(left // tw) - margin
|
|
385
457
|
max_tile_x = int(right // tw) + margin
|
|
386
|
-
min_tile_y = int(top
|
|
387
|
-
max_tile_y = int(bottom// th) + margin
|
|
458
|
+
min_tile_y = int(top // th) - margin
|
|
459
|
+
max_tile_y = int(bottom // th) + margin
|
|
388
460
|
|
|
389
461
|
shapes = []
|
|
390
462
|
for tile_y in range(min_tile_y, max_tile_y + 1):
|
|
@@ -399,7 +471,11 @@ class CollisionRunner:
|
|
|
399
471
|
tile_world_y = tile_y * th
|
|
400
472
|
for poly in tile_data.shapes:
|
|
401
473
|
if poly.is_valid():
|
|
402
|
-
shapes.append(
|
|
474
|
+
shapes.append(
|
|
475
|
+
poly.transform(
|
|
476
|
+
tile_world_x, tile_world_y, self.render_scale
|
|
477
|
+
)
|
|
478
|
+
)
|
|
403
479
|
return shapes
|
|
404
480
|
|
|
405
481
|
def _collides_at(
|
|
@@ -418,10 +494,10 @@ class CollisionRunner:
|
|
|
418
494
|
left, top, right, bottom = get_shape_bounds(sprite)
|
|
419
495
|
tw, th = self._eff_tw, self._eff_th
|
|
420
496
|
|
|
421
|
-
min_tile_x = int(left
|
|
497
|
+
min_tile_x = int(left // tw) - margin
|
|
422
498
|
max_tile_x = int(right // tw) + margin
|
|
423
|
-
min_tile_y = int(top
|
|
424
|
-
max_tile_y = int(bottom// th) + margin
|
|
499
|
+
min_tile_y = int(top // th) - margin
|
|
500
|
+
max_tile_y = int(bottom // th) + margin
|
|
425
501
|
|
|
426
502
|
for tile_y in range(min_tile_y, max_tile_y + 1):
|
|
427
503
|
for tile_x in range(min_tile_x, max_tile_x + 1):
|
|
@@ -434,7 +510,9 @@ class CollisionRunner:
|
|
|
434
510
|
ox = tile_x * tw
|
|
435
511
|
oy = tile_y * th
|
|
436
512
|
for poly in tile_data.shapes:
|
|
437
|
-
if poly.is_valid() and _check_sprite_polygon_offset(
|
|
513
|
+
if poly.is_valid() and _check_sprite_polygon_offset(
|
|
514
|
+
sprite, poly, ox, oy, self.render_scale
|
|
515
|
+
):
|
|
438
516
|
return True
|
|
439
517
|
return False
|
|
440
518
|
|
|
@@ -452,10 +530,10 @@ class CollisionRunner:
|
|
|
452
530
|
left, top, right, bottom = get_shape_bounds(sprite)
|
|
453
531
|
tw, th = self._eff_tw, self._eff_th
|
|
454
532
|
|
|
455
|
-
min_tile_x = int(left
|
|
533
|
+
min_tile_x = int(left // tw) - margin
|
|
456
534
|
max_tile_x = int(right // tw) + margin
|
|
457
|
-
min_tile_y = int(top
|
|
458
|
-
max_tile_y = int(bottom// th) + margin
|
|
535
|
+
min_tile_y = int(top // th) - margin
|
|
536
|
+
max_tile_y = int(bottom // th) + margin
|
|
459
537
|
|
|
460
538
|
for tile_y in range(min_tile_y, max_tile_y + 1):
|
|
461
539
|
for tile_x in range(min_tile_x, max_tile_x + 1):
|
|
@@ -468,7 +546,9 @@ class CollisionRunner:
|
|
|
468
546
|
ox = tile_x * tw
|
|
469
547
|
oy = tile_y * th
|
|
470
548
|
for poly in tile_data.shapes:
|
|
471
|
-
if poly.is_valid() and _check_sprite_polygon_offset(
|
|
549
|
+
if poly.is_valid() and _check_sprite_polygon_offset(
|
|
550
|
+
sprite, poly, ox, oy, self.render_scale
|
|
551
|
+
):
|
|
472
552
|
return (poly, ox, oy)
|
|
473
553
|
return None
|
|
474
554
|
|
|
@@ -498,11 +578,11 @@ class CollisionRunner:
|
|
|
498
578
|
CollisionResult with final position and collision info
|
|
499
579
|
"""
|
|
500
580
|
result = self._result
|
|
501
|
-
result.collided
|
|
581
|
+
result.collided = False
|
|
502
582
|
result.hit_wall_x = False
|
|
503
583
|
result.hit_wall_y = False
|
|
504
584
|
result.hit_ceiling = False
|
|
505
|
-
result.on_ground
|
|
585
|
+
result.on_ground = False
|
|
506
586
|
result.slide_vector = None
|
|
507
587
|
result.final_x = sprite.x
|
|
508
588
|
result.final_y = sprite.y
|
|
@@ -618,8 +698,11 @@ class CollisionRunner:
|
|
|
618
698
|
best_alignment = -1.0
|
|
619
699
|
|
|
620
700
|
for i in range(n):
|
|
621
|
-
v1x, v1y = vertices[i][0] * scale + ox,
|
|
622
|
-
v2x, v2y =
|
|
701
|
+
v1x, v1y = vertices[i][0] * scale + ox, vertices[i][1] * scale + oy
|
|
702
|
+
v2x, v2y = (
|
|
703
|
+
vertices[(i + 1) % n][0] * scale + ox,
|
|
704
|
+
vertices[(i + 1) % n][1] * scale + oy,
|
|
705
|
+
)
|
|
623
706
|
|
|
624
707
|
edge_x = v2x - v1x
|
|
625
708
|
edge_y = v2y - v1y
|
|
@@ -628,7 +711,7 @@ class CollisionRunner:
|
|
|
628
711
|
continue
|
|
629
712
|
|
|
630
713
|
normal_x = -edge_y / edge_len
|
|
631
|
-
normal_y =
|
|
714
|
+
normal_y = edge_x / edge_len
|
|
632
715
|
|
|
633
716
|
edge_mid_x = (v1x + v2x) * 0.5
|
|
634
717
|
edge_mid_y = (v1y + v2y) * 0.5
|
|
@@ -672,11 +755,11 @@ class CollisionRunner:
|
|
|
672
755
|
CollisionResult with final position and collision info
|
|
673
756
|
"""
|
|
674
757
|
result = self._result
|
|
675
|
-
result.collided
|
|
676
|
-
result.hit_wall_x
|
|
677
|
-
result.hit_wall_y
|
|
758
|
+
result.collided = False
|
|
759
|
+
result.hit_wall_x = False
|
|
760
|
+
result.hit_wall_y = False
|
|
678
761
|
result.hit_ceiling = False
|
|
679
|
-
result.on_ground
|
|
762
|
+
result.on_ground = False
|
|
680
763
|
result.slide_vector = None
|
|
681
764
|
result.final_x = sprite.x
|
|
682
765
|
result.final_y = sprite.y
|
|
@@ -697,21 +780,38 @@ class CollisionRunner:
|
|
|
697
780
|
|
|
698
781
|
# X axis
|
|
699
782
|
sprite.x = old_x + delta_x
|
|
783
|
+
# Lift above ground snap overlap so ground doesn't block horizontal movement
|
|
784
|
+
sprite.y = old_y - self.ground_snap_tolerance
|
|
785
|
+
stepped_up = False
|
|
700
786
|
if self._collides_at(sprite, tileset_collision, tile_map):
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
787
|
+
if delta_x != 0:
|
|
788
|
+
# Try stepping up onto slope/stairs
|
|
789
|
+
sprite.y = old_y - self.ground_snap_tolerance - self.step_height
|
|
790
|
+
if not self._collides_at(sprite, tileset_collision, tile_map):
|
|
791
|
+
sprite.y = old_y - self.step_height
|
|
792
|
+
stepped_up = True
|
|
793
|
+
else:
|
|
794
|
+
sprite.x = old_x
|
|
795
|
+
sprite.vx = 0.0
|
|
796
|
+
result.hit_wall_x = True
|
|
797
|
+
else:
|
|
798
|
+
sprite.x = old_x
|
|
799
|
+
sprite.vx = 0.0
|
|
800
|
+
result.hit_wall_x = True
|
|
704
801
|
|
|
705
802
|
# Y axis — check one-way platforms
|
|
706
|
-
|
|
803
|
+
if stepped_up:
|
|
804
|
+
sprite.y = sprite.y + delta_y
|
|
805
|
+
else:
|
|
806
|
+
sprite.y = old_y + delta_y
|
|
707
807
|
collided_y = False
|
|
708
808
|
|
|
709
809
|
left, top, right, bottom = get_shape_bounds(sprite)
|
|
710
810
|
tw, th = self._eff_tw, self._eff_th
|
|
711
|
-
min_tile_x = int(left
|
|
811
|
+
min_tile_x = int(left // tw) - 1
|
|
712
812
|
max_tile_x = int(right // tw) + 1
|
|
713
|
-
min_tile_y = int(top
|
|
714
|
-
max_tile_y = int(bottom// th) + 1
|
|
813
|
+
min_tile_y = int(top // th) - 1
|
|
814
|
+
max_tile_y = int(bottom // th) + 1
|
|
715
815
|
|
|
716
816
|
for tile_y in range(min_tile_y, max_tile_y + 1):
|
|
717
817
|
for tile_x in range(min_tile_x, max_tile_x + 1):
|
|
@@ -726,12 +826,16 @@ class CollisionRunner:
|
|
|
726
826
|
for poly in tile_data.shapes:
|
|
727
827
|
if not poly.is_valid():
|
|
728
828
|
continue
|
|
729
|
-
if not _check_sprite_polygon_offset(
|
|
829
|
+
if not _check_sprite_polygon_offset(
|
|
830
|
+
sprite, poly, ox, oy, self.render_scale
|
|
831
|
+
):
|
|
730
832
|
continue
|
|
731
833
|
if poly.one_way and sprite.vy > 0:
|
|
732
834
|
# one-way: only block if sprite was above the platform top
|
|
733
|
-
min_vy =
|
|
734
|
-
|
|
835
|
+
min_vy = (
|
|
836
|
+
min(v[1] for v in poly.vertices) * self.render_scale + oy
|
|
837
|
+
)
|
|
838
|
+
if old_y + (bottom - sprite.y) <= min_vy:
|
|
735
839
|
collided_y = True
|
|
736
840
|
break
|
|
737
841
|
elif not poly.one_way:
|
|
@@ -743,22 +847,185 @@ class CollisionRunner:
|
|
|
743
847
|
break
|
|
744
848
|
|
|
745
849
|
if collided_y:
|
|
746
|
-
|
|
850
|
+
if stepped_up:
|
|
851
|
+
step_y = old_y - self.step_height
|
|
852
|
+
lo, hi = step_y, old_y
|
|
853
|
+
for _ in range(8):
|
|
854
|
+
mid = (lo + hi) * 0.5
|
|
855
|
+
sprite.y = mid
|
|
856
|
+
if self._collides_at(sprite, tileset_collision, tile_map):
|
|
857
|
+
hi = mid
|
|
858
|
+
else:
|
|
859
|
+
lo = mid
|
|
860
|
+
sprite.y = lo
|
|
861
|
+
sprite.on_ground = True
|
|
862
|
+
result.on_ground = True
|
|
863
|
+
else:
|
|
864
|
+
sprite.y = old_y
|
|
747
865
|
if sprite.vy > 0:
|
|
866
|
+
fall_y = sprite.vy * dt
|
|
748
867
|
sprite.vy = 0.0
|
|
749
868
|
sprite.on_ground = True
|
|
750
869
|
result.on_ground = True
|
|
870
|
+
lo, hi = old_y, old_y + fall_y
|
|
871
|
+
for _ in range(8):
|
|
872
|
+
mid = (lo + hi) * 0.5
|
|
873
|
+
sprite.y = mid
|
|
874
|
+
if self._collides_at(sprite, tileset_collision, tile_map):
|
|
875
|
+
hi = mid
|
|
876
|
+
else:
|
|
877
|
+
lo = mid
|
|
878
|
+
sprite.y = lo
|
|
751
879
|
elif sprite.vy < 0:
|
|
752
880
|
sprite.vy = 0.0
|
|
753
881
|
result.hit_ceiling = True
|
|
882
|
+
else:
|
|
883
|
+
sprite.on_ground = True
|
|
884
|
+
result.on_ground = True
|
|
754
885
|
else:
|
|
755
886
|
sprite.on_ground = False
|
|
756
887
|
|
|
888
|
+
downward_travel = max(0.0, sprite.vy) * dt
|
|
889
|
+
if not sprite.on_ground and 0 <= downward_travel <= self.ground_snap_tolerance:
|
|
890
|
+
if self._collides_at(sprite, tileset_collision, tile_map):
|
|
891
|
+
sprite.on_ground = True
|
|
892
|
+
result.on_ground = True
|
|
893
|
+
sprite.vy = 0.0
|
|
894
|
+
else:
|
|
895
|
+
saved_y = sprite.y
|
|
896
|
+
sprite.y += self.ground_snap_tolerance
|
|
897
|
+
if self._collides_at(sprite, tileset_collision, tile_map):
|
|
898
|
+
sprite.on_ground = True
|
|
899
|
+
result.on_ground = True
|
|
900
|
+
sprite.vy = 0.0
|
|
901
|
+
else:
|
|
902
|
+
sprite.y = saved_y
|
|
903
|
+
|
|
757
904
|
result.final_x = sprite.x
|
|
758
905
|
result.final_y = sprite.y
|
|
759
906
|
result.collided = result.hit_wall_x or collided_y
|
|
760
907
|
return result
|
|
761
908
|
|
|
909
|
+
def move_platformer_with_slide(
|
|
910
|
+
self,
|
|
911
|
+
sprite: ICollidableSprite,
|
|
912
|
+
tileset_collision: TilesetCollision,
|
|
913
|
+
tile_map: dict,
|
|
914
|
+
dt: float,
|
|
915
|
+
input_x: float = 0.0,
|
|
916
|
+
jump_pressed: bool = False,
|
|
917
|
+
max_iterations: int = 4,
|
|
918
|
+
) -> CollisionResult:
|
|
919
|
+
"""
|
|
920
|
+
Move sprite with platformer physics and combined slope-sliding collision.
|
|
921
|
+
|
|
922
|
+
Uses iterative normal projection for movement resolution instead of
|
|
923
|
+
separate X/Y sweeps. This allows smooth movement on slopes — walking
|
|
924
|
+
up a slope ascends the player, walking down follows the surface.
|
|
925
|
+
|
|
926
|
+
One-way platforms are treated as solid (same as move_and_slide).
|
|
927
|
+
Use move_platformer() if you need one-way pass-through from below.
|
|
928
|
+
|
|
929
|
+
Args:
|
|
930
|
+
sprite: Sprite to move (must have vx, vy, on_ground attributes)
|
|
931
|
+
tileset_collision: Tileset collision data
|
|
932
|
+
tile_map: Dictionary mapping (tile_x, tile_y) to tile_id
|
|
933
|
+
dt: Delta time in seconds
|
|
934
|
+
input_x: Horizontal input (-1 to 1)
|
|
935
|
+
jump_pressed: Whether jump button is pressed
|
|
936
|
+
max_iterations: Max slope-slide iterations (default 4)
|
|
937
|
+
|
|
938
|
+
Returns:
|
|
939
|
+
CollisionResult with final position and collision info
|
|
940
|
+
"""
|
|
941
|
+
result = self._result
|
|
942
|
+
result.collided = False
|
|
943
|
+
result.hit_wall_x = False
|
|
944
|
+
result.hit_wall_y = False
|
|
945
|
+
result.hit_ceiling = False
|
|
946
|
+
result.on_ground = False
|
|
947
|
+
result.slide_vector = None
|
|
948
|
+
result.final_x = sprite.x
|
|
949
|
+
result.final_y = sprite.y
|
|
950
|
+
|
|
951
|
+
if not getattr(sprite, "on_ground", False):
|
|
952
|
+
sprite.vy += self.gravity * dt
|
|
953
|
+
if sprite.vy > self.max_fall_speed:
|
|
954
|
+
sprite.vy = self.max_fall_speed
|
|
955
|
+
|
|
956
|
+
if jump_pressed and getattr(sprite, "on_ground", False):
|
|
957
|
+
sprite.vy = self.jump_strength
|
|
958
|
+
|
|
959
|
+
sprite.vx = input_x * 200.0
|
|
960
|
+
|
|
961
|
+
delta_x = sprite.vx * dt
|
|
962
|
+
delta_y = sprite.vy * dt
|
|
963
|
+
old_x, old_y = sprite.x, sprite.y
|
|
964
|
+
|
|
965
|
+
if delta_x == 0 and delta_y == 0:
|
|
966
|
+
return result
|
|
967
|
+
|
|
968
|
+
motion_x, motion_y = delta_x, delta_y
|
|
969
|
+
collided = False
|
|
970
|
+
|
|
971
|
+
for _ in range(max_iterations):
|
|
972
|
+
if abs(motion_x) < 0.01 and abs(motion_y) < 0.01:
|
|
973
|
+
break
|
|
974
|
+
|
|
975
|
+
sprite.x = old_x + motion_x
|
|
976
|
+
sprite.y = old_y + motion_y
|
|
977
|
+
|
|
978
|
+
hit = self._first_colliding_shape(sprite, tileset_collision, tile_map)
|
|
979
|
+
if hit is None:
|
|
980
|
+
break
|
|
981
|
+
|
|
982
|
+
sprite.x = old_x
|
|
983
|
+
sprite.y = old_y
|
|
984
|
+
collided = True
|
|
985
|
+
|
|
986
|
+
poly, ox, oy = hit
|
|
987
|
+
normal = self._get_collision_normal_from_motion(
|
|
988
|
+
sprite, poly, ox, oy, motion_x, motion_y, self.render_scale
|
|
989
|
+
)
|
|
990
|
+
if normal is None:
|
|
991
|
+
break
|
|
992
|
+
|
|
993
|
+
dot = motion_x * normal[0] + motion_y * normal[1]
|
|
994
|
+
if dot < 0:
|
|
995
|
+
motion_x -= normal[0] * dot
|
|
996
|
+
motion_y -= normal[1] * dot
|
|
997
|
+
else:
|
|
998
|
+
break
|
|
999
|
+
|
|
1000
|
+
result.final_x = sprite.x
|
|
1001
|
+
result.final_y = sprite.y
|
|
1002
|
+
result.collided = collided
|
|
1003
|
+
|
|
1004
|
+
if collided:
|
|
1005
|
+
if sprite.vy >= 0:
|
|
1006
|
+
sprite.y += 1.0
|
|
1007
|
+
if self._collides_at(sprite, tileset_collision, tile_map):
|
|
1008
|
+
result.on_ground = True
|
|
1009
|
+
sprite.on_ground = True
|
|
1010
|
+
sprite.vy = 0.0
|
|
1011
|
+
else:
|
|
1012
|
+
sprite.on_ground = False
|
|
1013
|
+
sprite.y -= 1.0
|
|
1014
|
+
|
|
1015
|
+
if sprite.vy < 0:
|
|
1016
|
+
sprite.y -= 1.0
|
|
1017
|
+
if self._collides_at(sprite, tileset_collision, tile_map):
|
|
1018
|
+
result.hit_ceiling = True
|
|
1019
|
+
sprite.vy = 0.0
|
|
1020
|
+
sprite.y += 1.0
|
|
1021
|
+
|
|
1022
|
+
if abs(result.final_x - old_x) < 0.01 and abs(delta_x) > 0.01:
|
|
1023
|
+
result.hit_wall_x = True
|
|
1024
|
+
else:
|
|
1025
|
+
sprite.on_ground = False
|
|
1026
|
+
|
|
1027
|
+
return result
|
|
1028
|
+
|
|
762
1029
|
def move_rpg(
|
|
763
1030
|
self,
|
|
764
1031
|
sprite: ICollidableSprite,
|
|
@@ -783,11 +1050,11 @@ class CollisionRunner:
|
|
|
783
1050
|
CollisionResult with final position and collision info
|
|
784
1051
|
"""
|
|
785
1052
|
result = self._result
|
|
786
|
-
result.collided
|
|
787
|
-
result.hit_wall_x
|
|
788
|
-
result.hit_wall_y
|
|
1053
|
+
result.collided = False
|
|
1054
|
+
result.hit_wall_x = False
|
|
1055
|
+
result.hit_wall_y = False
|
|
789
1056
|
result.hit_ceiling = False
|
|
790
|
-
result.on_ground
|
|
1057
|
+
result.on_ground = False
|
|
791
1058
|
result.slide_vector = None
|
|
792
1059
|
result.final_x = sprite.x
|
|
793
1060
|
result.final_y = sprite.y
|
|
@@ -802,7 +1069,7 @@ class CollisionRunner:
|
|
|
802
1069
|
if self._collides_at(sprite, tileset_collision, tile_map):
|
|
803
1070
|
sprite.x = old_x
|
|
804
1071
|
sprite.y = old_y
|
|
805
|
-
result.collided
|
|
1072
|
+
result.collided = True
|
|
806
1073
|
result.hit_wall_x = delta_x != 0
|
|
807
1074
|
result.hit_wall_y = delta_y != 0
|
|
808
1075
|
else:
|
|
@@ -919,7 +1186,9 @@ class CollisionRunner:
|
|
|
919
1186
|
game_type = game_type.lower()
|
|
920
1187
|
|
|
921
1188
|
if game_type == "platformer":
|
|
922
|
-
runner = cls(
|
|
1189
|
+
runner = cls(
|
|
1190
|
+
tile_size, mode=MovementMode.PLATFORMER, render_scale=render_scale
|
|
1191
|
+
)
|
|
923
1192
|
runner.gravity = 800.0
|
|
924
1193
|
runner.max_fall_speed = 600.0
|
|
925
1194
|
runner.jump_strength = -400.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tilemap-parser
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.3
|
|
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
|
|
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-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/runtime/animation_player.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/src/tilemap_parser/runtime/object_collision.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tilemap_parser-3.1.1 → tilemap_parser-3.1.3}/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
|