tilemap-parser 5.0.2__tar.gz → 5.0.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-5.0.2 → tilemap_parser-5.0.3}/PKG-INFO +28 -84
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/README.md +5 -2
- tilemap_parser-5.0.3/pypi-readme.md +49 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/pyproject.toml +2 -2
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/__init__.py +5 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/parser/__init__.py +8 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/parser/particle.py +66 -11
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/__init__.py +10 -0
- tilemap_parser-5.0.3/src/tilemap_parser/runtime/particles.py +1012 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser.egg-info/PKG-INFO +28 -84
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser.egg-info/SOURCES.txt +1 -0
- tilemap_parser-5.0.3/tests/test_particle_emitter.py +601 -0
- tilemap_parser-5.0.2/src/tilemap_parser/runtime/particles.py +0 -587
- tilemap_parser-5.0.2/tests/test_particle_emitter.py +0 -74
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/LICENSE +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/setup.cfg +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/parser/animation.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/parser/collision.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/parser/collision_loader.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/parser/map_parse.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/parser/node_parse.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/parser/tmx_converter.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/animation_player.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/area_node.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/body.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/camera.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/collision/__init__.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/collision/hit.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/collision/manager.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/collision/shapes.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/collision_cache.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/map_loader.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/map_object.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/movement/__init__.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/movement/grounded.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/movement/platformer.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/movement/queries.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/movement/rpg.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/movement/runner.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/movement/slide.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/movement/types.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/navigation/__init__.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/navigation/nav_grid.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/navigation/path_follower.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/navigation/pathfinder.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/object_collision.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/polygon_query.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/protocols.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/renderer.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/tile_collision.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/runtime/world.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/utils/__init__.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser/utils/geometry.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser.egg-info/dependency_links.txt +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser.egg-info/requires.txt +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/src/tilemap_parser.egg-info/top_level.txt +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_body.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_camera.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_collision.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_geometry.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_gid_collision.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_integration_animation.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_integration_collision.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_integration_map_loader.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_map_loader.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_map_object.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_move_grounded.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_navigation.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_object_collision.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_object_surfaces.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_render_scale.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_tile_collision.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/tests/test_tile_layer_renderer_y_sort.py +0 -0
- {tilemap_parser-5.0.2 → tilemap_parser-5.0.3}/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.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
|
|
@@ -699,106 +699,50 @@ Dynamic: license-file
|
|
|
699
699
|
|
|
700
700
|
# tilemap-parser
|
|
701
701
|
|
|
702
|
-
Standalone parser/loader for tilemap-editor JSON maps, sprite animations, and collision detection runtime.
|
|
702
|
+
Standalone parser/loader for [tilemap-editor](https://pypi.org/project/tilemap-editor/) JSON maps, sprite animations, and a collision detection runtime.
|
|
703
703
|
|
|
704
|
-
##
|
|
704
|
+
## Install
|
|
705
705
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
- **Animation**: Frame-based sprite animation with `AnimationPlayer`
|
|
710
|
-
- **Particle system**: Configurable particle emitters with shapes, color transitions, alpha fades, gravity, and batch rendering with `ParticleSystem` + `SpriteBatchRenderer`
|
|
711
|
-
- **Collision (tile-based)**: Polygon collision detection for tilemaps with slide, platformer, and RPG movement modes via `CollisionRunner`
|
|
712
|
-
- **Collision (object-to-object)**: Spatial-grid mixed-shape collision detection (rect, circle, capsule, polygon) with layer filtering via `ObjectCollisionManager`
|
|
713
|
-
- **Capsule support**: Full capsule collision against all shape types
|
|
714
|
-
- **Hit helpers**: `CollisionHit.resolve()`, `involves()`, `other()` for ergonomic separation
|
|
706
|
+
```bash
|
|
707
|
+
pip install tilemap-parser
|
|
708
|
+
```
|
|
715
709
|
|
|
716
|
-
## Quick
|
|
710
|
+
## Quick start
|
|
717
711
|
|
|
718
712
|
```python
|
|
719
|
-
from tilemap_parser import load_map, TileLayerRenderer, Camera
|
|
713
|
+
from tilemap_parser import load_map, TileLayerRenderer, Camera, ParticleField, FOG_PROFILE
|
|
720
714
|
|
|
721
|
-
# Load map and set up renderer
|
|
722
715
|
game_data = load_map("path/to/map.json")
|
|
723
716
|
renderer = TileLayerRenderer(game_data)
|
|
724
717
|
|
|
725
|
-
# Set up camera
|
|
726
718
|
camera = Camera(800, 600, mode="centered")
|
|
727
719
|
camera.follow(player)
|
|
728
|
-
```
|
|
729
|
-
|
|
730
|
-
```python
|
|
731
|
-
from tilemap_parser import (
|
|
732
|
-
CollisionRunner, CollisionCache,
|
|
733
|
-
ObjectCollisionManager, CircleShape, RectangleShape,
|
|
734
|
-
ParticleSystem, ParticleSystemConfig
|
|
735
|
-
)
|
|
736
|
-
|
|
737
|
-
# Tile-based collision
|
|
738
|
-
cache = CollisionCache()
|
|
739
|
-
tileset = cache.get_tileset_collision("data/collision/tileset.collision.json")
|
|
740
|
-
runner = CollisionRunner.from_game_type("topdown", tile_size=(32, 32))
|
|
741
|
-
|
|
742
|
-
# Object-to-object collision
|
|
743
|
-
manager = ObjectCollisionManager()
|
|
744
|
-
manager.add_object(player)
|
|
745
|
-
for hit in manager.check_all_collisions():
|
|
746
|
-
hit.resolve() # separate both objects
|
|
747
720
|
|
|
748
|
-
#
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
lifetime_max=2.0,
|
|
755
|
-
speed_min=50,
|
|
756
|
-
speed_max=100,
|
|
757
|
-
direction=270,
|
|
758
|
-
spread=45,
|
|
759
|
-
start_color_r=255, start_color_g=200, start_color_b=100, start_color_a=255,
|
|
760
|
-
end_color_r=255, end_color_g=100, end_color_b=50, end_color_a=0,
|
|
761
|
-
alpha_fade="fade_out",
|
|
762
|
-
gravity_x=0,
|
|
763
|
-
gravity_y=50,
|
|
721
|
+
# Persistent fog: fill once, wrap forever — no particle churn
|
|
722
|
+
mist = ParticleField(
|
|
723
|
+
area=(0, 0, 800, 600),
|
|
724
|
+
profile=FOG_PROFILE, # plain data — copy and tweak for your own moods
|
|
725
|
+
color=(200, 50, 80),
|
|
726
|
+
global_alpha=0.5,
|
|
764
727
|
)
|
|
765
|
-
particles = ParticleSystem(particle_config)
|
|
766
728
|
```
|
|
767
729
|
|
|
768
|
-
##
|
|
769
|
-
|
|
770
|
-
A `PhysicsWorld` is a single space holding the tile layer plus solid `Body` objects (static walls, kinematic crates). Attach a `CollisionRunner` to the world once and every move method resolves tiles and bodies through it — no per-call tile arguments.
|
|
771
|
-
|
|
772
|
-
> Full contract — object↔world↔`move_*`↔tiles↔rendering: see [docs/physics-world.md](docs/physics-world.md) and `examples/physics-crate/main.py`.
|
|
773
|
-
|
|
774
|
-
```python
|
|
775
|
-
from tilemap_parser import (
|
|
776
|
-
Body, PhysicsWorld, CollisionRunner, RectangleShape,
|
|
777
|
-
TilemapData, TilesetCollision, CollisionCache,
|
|
778
|
-
)
|
|
779
|
-
|
|
780
|
-
cache = CollisionCache()
|
|
781
|
-
tileset = cache.get_tileset_collision("data/collision/tileset.collision.json")
|
|
782
|
-
world = PhysicsWorld.from_map(game_data, tileset) # adopts tile_size / render_scale
|
|
783
|
-
|
|
784
|
-
crate = Body(RectangleShape(width=16, height=16), x=320, y=480, mode="kinematic")
|
|
785
|
-
world.add_body(crate)
|
|
786
|
-
|
|
787
|
-
runner = CollisionRunner.from_world(world) # or runner.attach(world)
|
|
788
|
-
```
|
|
730
|
+
## Features
|
|
789
731
|
|
|
790
|
-
|
|
732
|
+
- **Map parsing** — tilemaps, layers, objects, and autotile data from JSON
|
|
733
|
+
- **Collision** — tile-based (`CollisionRunner`) and object-to-object (`ObjectCollisionManager`) with rect, circle, capsule, and polygon shapes
|
|
734
|
+
- **Physics** — `PhysicsWorld` with static/kinematic `Body` solids and one-way platforms
|
|
735
|
+
- **Particles** — wrapped continuous fields (`ParticleField`, `FOG_PROFILE`), burst emitters, alpha fades, and batch rendering (`SpriteBatchRenderer`)
|
|
736
|
+
- **Pathfinding** — A* `Pathfinder` over eroded `NavGrid`s
|
|
737
|
+
- **Animation** — frame-based `AnimationPlayer`, plus a `Camera` with centered/deadzone follow
|
|
738
|
+
- **Rendering** — chunked `TileLayerRenderer` and `LayerRenderStats`
|
|
791
739
|
|
|
792
|
-
|
|
793
|
-
# player pressed against the crate -> hand the player's vx over
|
|
794
|
-
crate.vx = player.vx
|
|
795
|
-
result = runner.move_grounded(crate, None, None, dt, velocity=(crate.vx, 0))
|
|
796
|
-
if result.hit_wall_x:
|
|
797
|
-
crate.vx = 0 # crate stopped against a tile wall or another body
|
|
798
|
-
```
|
|
740
|
+
## Requirements
|
|
799
741
|
|
|
800
|
-
|
|
742
|
+
- Python 3.10+
|
|
743
|
+
- `pygame-ce>=2.5` (installed automatically)
|
|
801
744
|
|
|
802
|
-
##
|
|
745
|
+
## Links
|
|
803
746
|
|
|
804
|
-
|
|
747
|
+
- **Documentation**: https://tilemap-parser.vercel.app/
|
|
748
|
+
- **Repository**: https://github.com/FluffyBrudy/tilemap-parser
|
|
@@ -100,6 +100,9 @@ if result.hit_wall_x:
|
|
|
100
100
|
|
|
101
101
|
`Body` accepts primitive shapes only (`RectangleShape`, `CircleShape`, `CapsuleShape`); use `MapObject` for polygon solids. Collision pairs honor both sides' `collision_layer` / `collision_mask`, and bodies are landing surfaces for platformer step-up (small crates are climbed, tall ones block like walls).
|
|
102
102
|
|
|
103
|
-
##
|
|
103
|
+
## Links
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
- **Docs**: [https://tilemap-parser.vercel.app/](https://tilemap-parser.vercel.app/)
|
|
106
|
+
- **Docs (deepwiki)**: [https://deepwiki.com/FluffyBrudy/tilemap-parser](https://deepwiki.com/FluffyBrudy/tilemap-parser)
|
|
107
|
+
- **Editor**: https://pypi.org/project/tilemap-editor/
|
|
108
|
+
- **Repository**: https://github.com/FluffyBrudy/tilemap-parser
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# tilemap-parser
|
|
2
|
+
|
|
3
|
+
Standalone parser/loader for [tilemap-editor](https://pypi.org/project/tilemap-editor/) JSON maps, sprite animations, and a collision detection runtime.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install tilemap-parser
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from tilemap_parser import load_map, TileLayerRenderer, Camera, ParticleField, FOG_PROFILE
|
|
15
|
+
|
|
16
|
+
game_data = load_map("path/to/map.json")
|
|
17
|
+
renderer = TileLayerRenderer(game_data)
|
|
18
|
+
|
|
19
|
+
camera = Camera(800, 600, mode="centered")
|
|
20
|
+
camera.follow(player)
|
|
21
|
+
|
|
22
|
+
# Persistent fog: fill once, wrap forever — no particle churn
|
|
23
|
+
mist = ParticleField(
|
|
24
|
+
area=(0, 0, 800, 600),
|
|
25
|
+
profile=FOG_PROFILE, # plain data — copy and tweak for your own moods
|
|
26
|
+
color=(200, 50, 80),
|
|
27
|
+
global_alpha=0.5,
|
|
28
|
+
)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- **Map parsing** — tilemaps, layers, objects, and autotile data from JSON
|
|
34
|
+
- **Collision** — tile-based (`CollisionRunner`) and object-to-object (`ObjectCollisionManager`) with rect, circle, capsule, and polygon shapes
|
|
35
|
+
- **Physics** — `PhysicsWorld` with static/kinematic `Body` solids and one-way platforms
|
|
36
|
+
- **Particles** — wrapped continuous fields (`ParticleField`, `FOG_PROFILE`), burst emitters, alpha fades, and batch rendering (`SpriteBatchRenderer`)
|
|
37
|
+
- **Pathfinding** — A* `Pathfinder` over eroded `NavGrid`s
|
|
38
|
+
- **Animation** — frame-based `AnimationPlayer`, plus a `Camera` with centered/deadzone follow
|
|
39
|
+
- **Rendering** — chunked `TileLayerRenderer` and `LayerRenderStats`
|
|
40
|
+
|
|
41
|
+
## Requirements
|
|
42
|
+
|
|
43
|
+
- Python 3.10+
|
|
44
|
+
- `pygame-ce>=2.5` (installed automatically)
|
|
45
|
+
|
|
46
|
+
## Links
|
|
47
|
+
|
|
48
|
+
- **Documentation**: https://tilemap-parser.vercel.app/
|
|
49
|
+
- **Repository**: https://github.com/FluffyBrudy/tilemap-parser
|
|
@@ -4,9 +4,9 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tilemap-parser"
|
|
7
|
-
version = "5.0.
|
|
7
|
+
version = "5.0.3"
|
|
8
8
|
description = "Standalone parser/loader for tilemap-editor JSON maps, sprite animations, and collision detection runtime."
|
|
9
|
-
readme = "
|
|
9
|
+
readme = "pypi-readme.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
license = { file = "LICENSE" }
|
|
12
12
|
authors = [{ name = "tilemap parser contributors" }]
|
|
@@ -46,8 +46,13 @@ __all__ = [
|
|
|
46
46
|
"TileLayerRenderer",
|
|
47
47
|
"TilesetCollision",
|
|
48
48
|
"TilemapData",
|
|
49
|
+
"FOG_PROFILE",
|
|
50
|
+
"FieldLayerSpec",
|
|
51
|
+
"FieldProfile",
|
|
49
52
|
"Particle",
|
|
50
53
|
"ParticleEmitter",
|
|
54
|
+
"ParticleField",
|
|
55
|
+
"ParticleFieldLayer",
|
|
51
56
|
"ParticleRenderer",
|
|
52
57
|
"ParticleSystem",
|
|
53
58
|
"ParticleSystemConfig",
|
|
@@ -51,6 +51,10 @@ from .particle import (
|
|
|
51
51
|
ALPHA_FADE_MODES,
|
|
52
52
|
EMISSION_SHAPES,
|
|
53
53
|
PARTICLE_SHAPES,
|
|
54
|
+
AlphaFadeMode,
|
|
55
|
+
EmissionShape,
|
|
56
|
+
FieldQuality,
|
|
57
|
+
ParticleShape,
|
|
54
58
|
ParticleSystemConfig,
|
|
55
59
|
parse_particle_dict,
|
|
56
60
|
parse_particle_file,
|
|
@@ -105,6 +109,10 @@ __all__ = [
|
|
|
105
109
|
"ALPHA_FADE_MODES",
|
|
106
110
|
"EMISSION_SHAPES",
|
|
107
111
|
"PARTICLE_SHAPES",
|
|
112
|
+
"AlphaFadeMode",
|
|
113
|
+
"EmissionShape",
|
|
114
|
+
"FieldQuality",
|
|
115
|
+
"ParticleShape",
|
|
108
116
|
"ParticleSystemConfig",
|
|
109
117
|
"parse_particle_dict",
|
|
110
118
|
"parse_particle_file",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import math
|
|
4
5
|
from dataclasses import dataclass, field
|
|
5
6
|
from pathlib import Path
|
|
6
|
-
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
7
|
+
from typing import Any, Dict, List, Literal, Optional, Tuple, Union, cast, get_args
|
|
7
8
|
|
|
8
9
|
from .map_parse import (
|
|
9
10
|
JsonDict,
|
|
@@ -18,16 +19,21 @@ from .map_parse import (
|
|
|
18
19
|
)
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
EmissionShape = Literal["point", "rect", "circle", "line"]
|
|
23
|
+
ParticleShape = Literal["circle", "square", "diamond", "star", "sparkle", "smoke", "heart", "line", "fog"]
|
|
24
|
+
AlphaFadeMode = Literal["none", "fade_out", "fade_in", "fade_both"]
|
|
25
|
+
FieldQuality = Literal["low", "medium", "high"]
|
|
26
|
+
|
|
21
27
|
EMISSION_SHAPES = ["point", "rect", "circle", "line"]
|
|
22
|
-
PARTICLE_SHAPES =
|
|
23
|
-
ALPHA_FADE_MODES =
|
|
28
|
+
PARTICLE_SHAPES = list(get_args(ParticleShape))
|
|
29
|
+
ALPHA_FADE_MODES = list(get_args(AlphaFadeMode))
|
|
24
30
|
|
|
25
31
|
|
|
26
32
|
@dataclass
|
|
27
33
|
class ParticleSystemConfig:
|
|
28
34
|
name: str
|
|
29
|
-
emission_shape:
|
|
30
|
-
particle_shape:
|
|
35
|
+
emission_shape: EmissionShape = "point"
|
|
36
|
+
particle_shape: ParticleShape = "circle"
|
|
31
37
|
particle_size_min: int = 2
|
|
32
38
|
particle_size_max: int = 6
|
|
33
39
|
spawn_rate: int = 20
|
|
@@ -51,7 +57,9 @@ class ParticleSystemConfig:
|
|
|
51
57
|
start_scale: float = 1.0
|
|
52
58
|
end_scale: float = 0.3
|
|
53
59
|
rotation_speed: float = 0.0
|
|
54
|
-
alpha_fade:
|
|
60
|
+
alpha_fade: AlphaFadeMode = "fade_out"
|
|
61
|
+
fade_peak_alpha: Optional[int] = None
|
|
62
|
+
wrap: bool = False
|
|
55
63
|
|
|
56
64
|
def apply_render_scale(self, scale: float) -> None:
|
|
57
65
|
"""Scale dimensionful fields by *scale* (typically ``render_scale``).
|
|
@@ -70,8 +78,45 @@ class ParticleSystemConfig:
|
|
|
70
78
|
self.gravity_x *= scale
|
|
71
79
|
self.gravity_y *= scale
|
|
72
80
|
|
|
81
|
+
def fill_area(self, w: float, h: float) -> float:
|
|
82
|
+
"""Effective fill area of the emission shape over a ``w x h`` rect.
|
|
83
|
+
|
|
84
|
+
Rect and line fill their whole band; circle fills only the
|
|
85
|
+
inscribed disc (``pi * (min(w, h) / 2) ** 2``), so density math
|
|
86
|
+
stays honest for non-rect shapes. ``point`` has no area and
|
|
87
|
+
raises.
|
|
88
|
+
"""
|
|
89
|
+
if self.emission_shape == "circle":
|
|
90
|
+
radius = min(w, h) / 2
|
|
91
|
+
return math.pi * radius * radius
|
|
92
|
+
if self.emission_shape == "point":
|
|
93
|
+
raise ValueError(
|
|
94
|
+
"emission_shape 'point' has no fill area; use rect/circle/line "
|
|
95
|
+
"or a wider emission rect"
|
|
96
|
+
)
|
|
97
|
+
return w * h
|
|
98
|
+
|
|
99
|
+
def count_for_coverage(self, coverage: float, w: float, h: float) -> int:
|
|
100
|
+
"""Particles needed so sheets cover ``coverage`` of the ``w x h`` area.
|
|
101
|
+
|
|
102
|
+
``coverage`` is dimensionless (0.5 = half the area). Count is
|
|
103
|
+
``coverage * fill_area / mean_particle_area``, where the particle
|
|
104
|
+
area uses the shape's fill factor (circle sheets are roughly
|
|
105
|
+
``pi/4`` of their bounding square; everything else uses the full
|
|
106
|
+
square). Not capped by ``max_particles`` — cap it yourself or use
|
|
107
|
+
``ParticleSystem.emit_field``, which does.
|
|
108
|
+
"""
|
|
109
|
+
if coverage <= 0:
|
|
110
|
+
raise ValueError(f"coverage must be > 0, got {coverage}")
|
|
111
|
+
mean_size = (self.particle_size_min + self.particle_size_max) / 2
|
|
112
|
+
if mean_size <= 0:
|
|
113
|
+
return 0
|
|
114
|
+
fill = math.pi / 4 if self.particle_shape == "circle" else 1.0
|
|
115
|
+
area = self.fill_area(w, h)
|
|
116
|
+
return max(0, round(coverage * area / (mean_size * mean_size * fill)))
|
|
117
|
+
|
|
73
118
|
def to_dict(self) -> JsonDict:
|
|
74
|
-
|
|
119
|
+
d = {
|
|
75
120
|
"emission_shape": self.emission_shape,
|
|
76
121
|
"particle_shape": self.particle_shape,
|
|
77
122
|
"particle_size_min": self.particle_size_min,
|
|
@@ -98,7 +143,11 @@ class ParticleSystemConfig:
|
|
|
98
143
|
"end_scale": self.end_scale,
|
|
99
144
|
"rotation_speed": self.rotation_speed,
|
|
100
145
|
"alpha_fade": self.alpha_fade,
|
|
146
|
+
"wrap": self.wrap,
|
|
101
147
|
}
|
|
148
|
+
if self.fade_peak_alpha is not None:
|
|
149
|
+
d["fade_peak_alpha"] = self.fade_peak_alpha
|
|
150
|
+
return d
|
|
102
151
|
|
|
103
152
|
@classmethod
|
|
104
153
|
def from_dict(cls, d: JsonDict, name: str = "") -> "ParticleSystemConfig":
|
|
@@ -106,18 +155,18 @@ class ParticleSystemConfig:
|
|
|
106
155
|
return d.get(key, default)
|
|
107
156
|
|
|
108
157
|
cfg = cls(name=name)
|
|
109
|
-
cfg.emission_shape = str(g("emission_shape", "point"))
|
|
158
|
+
cfg.emission_shape = cast(EmissionShape, str(g("emission_shape", "point")))
|
|
110
159
|
if cfg.emission_shape not in EMISSION_SHAPES:
|
|
111
160
|
cfg.emission_shape = "point"
|
|
112
|
-
cfg.particle_shape = str(g("particle_shape", "circle"))
|
|
161
|
+
cfg.particle_shape = cast(ParticleShape, str(g("particle_shape", "circle")))
|
|
113
162
|
if cfg.particle_shape not in PARTICLE_SHAPES:
|
|
114
163
|
cfg.particle_shape = "circle"
|
|
115
|
-
cfg.alpha_fade = str(g("alpha_fade", "fade_out"))
|
|
164
|
+
cfg.alpha_fade = cast(AlphaFadeMode, str(g("alpha_fade", "fade_out")))
|
|
116
165
|
if cfg.alpha_fade not in ALPHA_FADE_MODES:
|
|
117
166
|
cfg.alpha_fade = "fade_out"
|
|
118
167
|
cfg.particle_size_min = max(1, int(g("particle_size_min", 2)))
|
|
119
168
|
cfg.particle_size_max = max(cfg.particle_size_min, int(g("particle_size_max", 6)))
|
|
120
|
-
cfg.spawn_rate = max(
|
|
169
|
+
cfg.spawn_rate = max(0, int(g("spawn_rate", 20)))
|
|
121
170
|
cfg.max_particles = max(1, int(g("max_particles", 100)))
|
|
122
171
|
cfg.lifetime_min = max(0.1, float(g("lifetime_min", 0.5)))
|
|
123
172
|
cfg.lifetime_max = max(cfg.lifetime_min, float(g("lifetime_max", 2.0)))
|
|
@@ -138,6 +187,12 @@ class ParticleSystemConfig:
|
|
|
138
187
|
cfg.start_scale = max(0.1, float(g("start_scale", 1.0)))
|
|
139
188
|
cfg.end_scale = max(0.1, float(g("end_scale", 0.3)))
|
|
140
189
|
cfg.rotation_speed = float(g("rotation_speed", 0))
|
|
190
|
+
cfg.wrap = bool(g("wrap", False))
|
|
191
|
+
raw_peak = d.get("fade_peak_alpha")
|
|
192
|
+
if raw_peak is None:
|
|
193
|
+
cfg.fade_peak_alpha = None
|
|
194
|
+
else:
|
|
195
|
+
cfg.fade_peak_alpha = max(0, min(255, int(raw_peak)))
|
|
141
196
|
return cfg
|
|
142
197
|
|
|
143
198
|
|
|
@@ -26,9 +26,14 @@ from .renderer import LayerRenderStats, TileLayerRenderer
|
|
|
26
26
|
from .area_node import AreaNode
|
|
27
27
|
from . import navigation
|
|
28
28
|
from .particles import (
|
|
29
|
+
FOG_PROFILE,
|
|
30
|
+
FieldLayerSpec,
|
|
31
|
+
FieldProfile,
|
|
29
32
|
Particle,
|
|
30
33
|
ParticleEmitter,
|
|
31
34
|
ParticleEmitterNode,
|
|
35
|
+
ParticleField,
|
|
36
|
+
ParticleFieldLayer,
|
|
32
37
|
ParticleRenderer,
|
|
33
38
|
ParticleSystem,
|
|
34
39
|
SpriteBatchRenderer,
|
|
@@ -64,9 +69,14 @@ __all__ = [
|
|
|
64
69
|
"load_map_objects",
|
|
65
70
|
"load_object_collision",
|
|
66
71
|
"load_tileset_collision",
|
|
72
|
+
"FOG_PROFILE",
|
|
73
|
+
"FieldLayerSpec",
|
|
74
|
+
"FieldProfile",
|
|
67
75
|
"Particle",
|
|
68
76
|
"ParticleEmitter",
|
|
69
77
|
"ParticleEmitterNode",
|
|
78
|
+
"ParticleField",
|
|
79
|
+
"ParticleFieldLayer",
|
|
70
80
|
"ParticleRenderer",
|
|
71
81
|
"ParticleSystem",
|
|
72
82
|
"PhysicsWorld",
|