spawnpoint-studio 0.3.2__tar.gz → 0.3.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.
- {spawnpoint_studio-0.3.2/compiler/spawnpoint_studio.egg-info → spawnpoint_studio-0.3.3}/PKG-INFO +12 -9
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/README.md +11 -8
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/cli.py +9 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/cartoon.py +3 -0
- spawnpoint_studio-0.3.3/compiler/spawnpoint/sprites/cartoon_monsters.py +152 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/gallery.py +41 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/generate.py +12 -2
- spawnpoint_studio-0.3.3/compiler/spawnpoint/sprites/monsters.py +220 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/assemble.py +16 -8
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/prompts.py +2 -1
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3/compiler/spawnpoint_studio.egg-info}/PKG-INFO +12 -9
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint_studio.egg-info/SOURCES.txt +2 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_sprites.py +21 -3
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_studio.py +2 -1
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/castle_defense.json +2 -1
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/coin_hopper.json +3 -2
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/dino_eggs.json +2 -1
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/knight_quest.json +4 -2
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/neon_arena.json +3 -2
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/night_survivor.json +3 -2
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/ninja_rescue.json +3 -2
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/pixel_arena.json +3 -2
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/pixel_hopper.json +3 -2
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/star_run.json +2 -1
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/sunny_farm.json +3 -2
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/pyproject.toml +1 -1
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/LICENSE +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/MANIFEST.in +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/__init__.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/analyze.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/build.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/cockpit/__init__.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/cockpit/server.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/cockpit/static/app.css +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/cockpit/static/app.js +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/cockpit/static/index.html +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/demo.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/demo_touch.js +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/diagnostics.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/expr.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/gif.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/godot.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/graph.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/level.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/llm_server.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/manifest.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/playtest/__init__.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/playtest/report.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/playtest/runner.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/playtest/tuner.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/project.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/py.typed +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/rules.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sfx.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/__init__.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/ai.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/chibi.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/contour.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/creatures.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/importer.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/items.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/motion.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/palettes.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/resolve.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/scenery.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/spriteset.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/tiles.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/__init__.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/brief.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/creative.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/dataset.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/edits.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/genres.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/levelgen.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/llm.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/orchestrator.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/web.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/webcheck.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio_cli.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint_studio.egg-info/dependency_links.txt +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint_studio.egg-info/entry_points.txt +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint_studio.egg-info/requires.txt +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint_studio.egg-info/top_level.txt +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/conftest.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_ai_sprites.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_analyze.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_cockpit.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_demo.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_e2e.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_expr.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_genres.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_gif.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_godot_library.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_graph.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_level.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_live_llm.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_manifest.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_playtest.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_rules.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_sfx.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_web.py +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/aim_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/animator_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/base_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/bubble_board_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/chase_ai_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/collectible_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/crop_pest_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/experience_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/farm_plot_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/farmer_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/flight_path_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/gate_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/ground_patch_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/health_bar_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/health_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/hitbox_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/hurtbox_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/juice_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/level_up_menu_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/market_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/path_follower_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/patrol_ai_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/platformer_movement_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/player_input_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/projectile_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/shmup_ship_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/spawner_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/stomp_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/top_down_movement_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/tower_brain_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/tower_builder_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/tower_slot_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/trigger_zone_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/wave_spawner_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/weapon_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/xp_orb_component.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/manifest.json +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/camera_rig.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/cockpit.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/entity_core.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/fx.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/game_state.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/hud.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/level.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/sfx.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/sp.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/world.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/shaders/flash.gdshader +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/shaders/floor_grid.gdshader +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/build.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/build.tscn +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/capture.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/capture.tscn +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/dump_manifest.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/dump_manifest.tscn +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/playtest.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/playtest.tscn +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/playtest_nav.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/record.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/record.tscn +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/smoke.gd +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/smoke.tscn +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/setup.cfg +0 -0
- {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/setup.py +0 -0
{spawnpoint_studio-0.3.2/compiler/spawnpoint_studio.egg-info → spawnpoint_studio-0.3.3}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spawnpoint-studio
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Turn a sentence into a juicy, playable Godot 4 game — locally, on a consumer GPU.
|
|
5
5
|
Author: smoky1496
|
|
6
6
|
License: MIT
|
|
@@ -33,7 +33,7 @@ Dynamic: license-file
|
|
|
33
33
|
[](LICENSE)
|
|
34
34
|
|
|
35
35
|
<p align="center">
|
|
36
|
-
<img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/knight-quest.gif" width="48%" alt="Knight's Quest: a
|
|
36
|
+
<img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/knight-quest.gif" width="48%" alt="Knight's Quest: a sword platformer">
|
|
37
37
|
<img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/castle-defense.gif" width="48%" alt="Castle Defense: a tower defense game">
|
|
38
38
|
<img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/ninja-rescue.gif" width="48%" alt="Ninja Rescue: a platformer where a ninja frees his mother">
|
|
39
39
|
<img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/dino-eggs.gif" width="48%" alt="Dino Eggs: a bubble shooter with dinosaur eggs">
|
|
@@ -42,10 +42,10 @@ Dynamic: license-file
|
|
|
42
42
|
<img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/star-run.gif" width="48%" alt="Star Run: a side-scrolling shooter with power-ups and a boss">
|
|
43
43
|
</p>
|
|
44
44
|
<p align="center"><sub>Seven of the demo games, each one a small JSON graph in <code>examples/</code> built from the
|
|
45
|
-
same component library: generated
|
|
45
|
+
same component library: generated pixel art, collision traced from the sprites, juice on by default.
|
|
46
46
|
Recorded with <code>spawnpoint gif</code> (autopilot).</sub></p>
|
|
47
47
|
|
|
48
|
-
<p align="center"><b><a href="https://leluong141996-dev.github.io/Spawn-Point/">▶ Play the demos in your browser</a></b>: a Mario-style platformer, a
|
|
48
|
+
<p align="center"><b><a href="https://leluong141996-dev.github.io/Spawn-Point/">▶ Play the demos in your browser</a></b>: a Mario-style platformer, a
|
|
49
49
|
knight with a sword, tower defense, a ninja rescue, a dinosaur-egg bubble shooter, a survivor with level-ups, a farming
|
|
50
50
|
game, an arena shooter and a side-scrolling space shooter. No install needed, and
|
|
51
51
|
they work on phones too (touch controls).</p>
|
|
@@ -169,10 +169,12 @@ use `--no-web` to skip and play natively with `spawnpoint run`.
|
|
|
169
169
|
|
|
170
170
|
## Sprites: generated or your own
|
|
171
171
|
|
|
172
|
-
Layer 2 replaces placeholder shapes with **procedural
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
172
|
+
Layer 2 replaces placeholder shapes with **procedural pixel art** — no assets, no GPU: chibi people with big heads, monsters with faces (a biting plant, a mushroom, a snail, a flame spirit, a reaper, an alien, a dragon, a ghost...), items, props, themed tiles and a parallax sky. Spawn Point makes pixel art games only. (A graph can still ask for the smooth `"art_style": "cartoon"` look by hand, but games made from a prompt are always pixel art.)
|
|
173
|
+
|
|
174
|
+
<p align="center"><img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/parade-heroes.gif" width="48%" alt="Animated heroes: knight, wizard, ranger, viking, king, princess, ninja..."> <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/parade-monsters.gif" width="48%" alt="Animated monsters: slime, frog, biting plant, mushroom, snail, flame..."></p>
|
|
175
|
+
<p align="center"><sub>Every character and monster is drawn by plain Python from its name (<code>spawnpoint sprites parade --set heroes|monsters|items</code>).</sub></p>
|
|
176
|
+
|
|
177
|
+
The
|
|
176
178
|
kind follows the entity (`Goblin` → humanoid, `FireBat` → flyer, `LaserBot` → robot, `Crystal` → gem…)
|
|
177
179
|
and details follow the name (`Ninja` → mask, `Fox` → ears and tail, `Spider` → legs, a sword-wielder holds a sword).
|
|
178
180
|
Every sprite has idle / run / jump / fall / hurt animations driven automatically by an `Animator` component, plus
|
|
@@ -204,7 +206,8 @@ spawnpoint sprites import hero.png --grid 32x32 --anim idle=0-3 --anim run=4-9
|
|
|
204
206
|
spawnpoint sprites import sheet.png --rows idle,run,jump # auto-sliced by transparent gutters
|
|
205
207
|
spawnpoint sprites import kenney_frames/ # idle_0.png, walk_1.png, ...
|
|
206
208
|
spawnpoint sprites preview assets/sprites/Knight.sprite.json # contact sheet + collision outline
|
|
207
|
-
spawnpoint sprites gallery --style
|
|
209
|
+
spawnpoint sprites gallery --style fine -o gallery.png # every kind on one sheet, for review
|
|
210
|
+
spawnpoint sprites parade --set monsters -o monsters.gif # an animated sheet of characters, monsters or items
|
|
208
211
|
```
|
|
209
212
|
|
|
210
213
|
Names pick the art: `Knight` gets a helmet and shield, `SkeletonArcher` a skull and a bow (and shoots
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/knight-quest.gif" width="48%" alt="Knight's Quest: a
|
|
12
|
+
<img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/knight-quest.gif" width="48%" alt="Knight's Quest: a sword platformer">
|
|
13
13
|
<img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/castle-defense.gif" width="48%" alt="Castle Defense: a tower defense game">
|
|
14
14
|
<img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/ninja-rescue.gif" width="48%" alt="Ninja Rescue: a platformer where a ninja frees his mother">
|
|
15
15
|
<img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/dino-eggs.gif" width="48%" alt="Dino Eggs: a bubble shooter with dinosaur eggs">
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
<img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/star-run.gif" width="48%" alt="Star Run: a side-scrolling shooter with power-ups and a boss">
|
|
19
19
|
</p>
|
|
20
20
|
<p align="center"><sub>Seven of the demo games, each one a small JSON graph in <code>examples/</code> built from the
|
|
21
|
-
same component library: generated
|
|
21
|
+
same component library: generated pixel art, collision traced from the sprites, juice on by default.
|
|
22
22
|
Recorded with <code>spawnpoint gif</code> (autopilot).</sub></p>
|
|
23
23
|
|
|
24
|
-
<p align="center"><b><a href="https://leluong141996-dev.github.io/Spawn-Point/">▶ Play the demos in your browser</a></b>: a Mario-style platformer, a
|
|
24
|
+
<p align="center"><b><a href="https://leluong141996-dev.github.io/Spawn-Point/">▶ Play the demos in your browser</a></b>: a Mario-style platformer, a
|
|
25
25
|
knight with a sword, tower defense, a ninja rescue, a dinosaur-egg bubble shooter, a survivor with level-ups, a farming
|
|
26
26
|
game, an arena shooter and a side-scrolling space shooter. No install needed, and
|
|
27
27
|
they work on phones too (touch controls).</p>
|
|
@@ -145,10 +145,12 @@ use `--no-web` to skip and play natively with `spawnpoint run`.
|
|
|
145
145
|
|
|
146
146
|
## Sprites: generated or your own
|
|
147
147
|
|
|
148
|
-
Layer 2 replaces placeholder shapes with **procedural
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
Layer 2 replaces placeholder shapes with **procedural pixel art** — no assets, no GPU: chibi people with big heads, monsters with faces (a biting plant, a mushroom, a snail, a flame spirit, a reaper, an alien, a dragon, a ghost...), items, props, themed tiles and a parallax sky. Spawn Point makes pixel art games only. (A graph can still ask for the smooth `"art_style": "cartoon"` look by hand, but games made from a prompt are always pixel art.)
|
|
149
|
+
|
|
150
|
+
<p align="center"><img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/parade-heroes.gif" width="48%" alt="Animated heroes: knight, wizard, ranger, viking, king, princess, ninja..."> <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/parade-monsters.gif" width="48%" alt="Animated monsters: slime, frog, biting plant, mushroom, snail, flame..."></p>
|
|
151
|
+
<p align="center"><sub>Every character and monster is drawn by plain Python from its name (<code>spawnpoint sprites parade --set heroes|monsters|items</code>).</sub></p>
|
|
152
|
+
|
|
153
|
+
The
|
|
152
154
|
kind follows the entity (`Goblin` → humanoid, `FireBat` → flyer, `LaserBot` → robot, `Crystal` → gem…)
|
|
153
155
|
and details follow the name (`Ninja` → mask, `Fox` → ears and tail, `Spider` → legs, a sword-wielder holds a sword).
|
|
154
156
|
Every sprite has idle / run / jump / fall / hurt animations driven automatically by an `Animator` component, plus
|
|
@@ -180,7 +182,8 @@ spawnpoint sprites import hero.png --grid 32x32 --anim idle=0-3 --anim run=4-9
|
|
|
180
182
|
spawnpoint sprites import sheet.png --rows idle,run,jump # auto-sliced by transparent gutters
|
|
181
183
|
spawnpoint sprites import kenney_frames/ # idle_0.png, walk_1.png, ...
|
|
182
184
|
spawnpoint sprites preview assets/sprites/Knight.sprite.json # contact sheet + collision outline
|
|
183
|
-
spawnpoint sprites gallery --style
|
|
185
|
+
spawnpoint sprites gallery --style fine -o gallery.png # every kind on one sheet, for review
|
|
186
|
+
spawnpoint sprites parade --set monsters -o monsters.gif # an animated sheet of characters, monsters or items
|
|
184
187
|
```
|
|
185
188
|
|
|
186
189
|
Names pick the art: `Knight` gets a helmet and shield, `SkeletonArcher` a skull and a bow (and shoots
|
|
@@ -124,6 +124,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
124
124
|
sga = sps.add_parser("gallery", help="render one contact sheet of every procedural kind, for review")
|
|
125
125
|
sga.add_argument("--style", choices=["pixel", "fine", "cartoon"], default="fine")
|
|
126
126
|
sga.add_argument("-o", "--out", type=Path, default=Path("sprite-gallery.png"))
|
|
127
|
+
spa = sps.add_parser("parade", help="render an animated sheet (GIF) of characters, monsters or items")
|
|
128
|
+
spa.add_argument("--set", dest="which", choices=["heroes", "monsters", "items"], default="heroes")
|
|
129
|
+
spa.add_argument("-o", "--out", type=Path, default=Path("sprite-parade.gif"))
|
|
127
130
|
|
|
128
131
|
ds = sub.add_parser("dataset", help="recorded fine-tuning data")
|
|
129
132
|
ds.add_argument("action", choices=["stats", "path"])
|
|
@@ -275,6 +278,12 @@ def _sprites(args) -> int:
|
|
|
275
278
|
for k, spec in gen.KINDS.items():
|
|
276
279
|
print(f"{k:<8} {spec.size[0]}x{spec.size[1]} px animations: {', '.join(spec.anims)}")
|
|
277
280
|
return 0
|
|
281
|
+
if args.sprites_cmd == "parade":
|
|
282
|
+
from .sprites.gallery import parade
|
|
283
|
+
frames = parade(args.which)
|
|
284
|
+
frames[0].save(args.out, save_all=True, append_images=frames[1:], duration=100, loop=0, optimize=True)
|
|
285
|
+
print(f"Wrote {args.out}")
|
|
286
|
+
return 0
|
|
278
287
|
if args.sprites_cmd == "gallery":
|
|
279
288
|
from .sprites.gallery import gallery
|
|
280
289
|
gallery(args.style).save(args.out)
|
|
@@ -972,6 +972,9 @@ PAINTERS = {"hero": draw_hero, "slime": draw_blob, "hopper": draw_blob, "bug": d
|
|
|
972
972
|
"beast": draw_beast, "spider": draw_spider, "dragon": draw_dragon, "ghost": draw_ghost, "snake": draw_snake,
|
|
973
973
|
"key": draw_key, "potion": draw_potion, "scroll": draw_scroll, "bomb": draw_bomb, "chest": draw_chest,
|
|
974
974
|
"arrow": draw_arrow, "tree": draw_tree, "bush": draw_bush, "rock": draw_rock}
|
|
975
|
+
from . import cartoon_monsters # noqa: E402 (needs Painter and the helpers above)
|
|
976
|
+
|
|
977
|
+
PAINTERS.update(cartoon_monsters.PAINTERS)
|
|
975
978
|
OUTLINE = {"bullet": 0.8, "slash": 0.8, "coin": 1.0, "gem": 1.0, "star": 1.1, "heart": 1.1, "spikes": 1.0, "orb": 1.0,
|
|
976
979
|
"key": 1.0, "potion": 1.0, "scroll": 1.0, "bomb": 1.0, "arrow": 0.8}
|
|
977
980
|
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Cartoon twins of the monsters in monsters.py: the same characters in the smooth vector style."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
|
|
7
|
+
from .cartoon import EYE, WHITE, Painter, Pose, _hexrgb, _shade
|
|
8
|
+
from . import monsters
|
|
9
|
+
|
|
10
|
+
LEAF = _hexrgb(monsters.LEAF)
|
|
11
|
+
MOUTH = _hexrgb(monsters.MOUTH)
|
|
12
|
+
TONGUE = _hexrgb(monsters.TONGUE)
|
|
13
|
+
STEM = _hexrgb(monsters.STEM)
|
|
14
|
+
CLOAK = _hexrgb(monsters.CLOAK)
|
|
15
|
+
BONE = _hexrgb(monsters.BONE)
|
|
16
|
+
FLAME_MID = _hexrgb(monsters.FLAME_MID)
|
|
17
|
+
FLAME_CORE = _hexrgb(monsters.FLAME_CORE)
|
|
18
|
+
SNAIL_BODY = _hexrgb(monsters.SNAIL_BODY)
|
|
19
|
+
WOOD = _hexrgb(monsters.WOOD)
|
|
20
|
+
STEEL = _hexrgb(monsters.STEEL)
|
|
21
|
+
GLOW = (255, 90, 90)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _top_half(cx: float, cy: float, rx: float, ry: float, n: int = 24) -> list[tuple[float, float]]:
|
|
25
|
+
return [(cx + rx * math.cos(math.pi + math.pi * k / n), cy + ry * math.sin(math.pi + math.pi * k / n)) for k in range(n + 1)]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def draw_plant(P: Painter, p: Pose) -> None:
|
|
29
|
+
c = P.p
|
|
30
|
+
k = p.i % 4
|
|
31
|
+
sw = (0, 2, 0, -2)[k]
|
|
32
|
+
hurt = p.anim == "hurt"
|
|
33
|
+
gape = 2 if hurt else {"idle": (6, 10, 14, 8)[k], "run": (10, 4, 10, 4)[k]}.get(p.anim, 8)
|
|
34
|
+
P.line([(28, 62), (28 + sw, 36)], 9, LEAF, top=False)
|
|
35
|
+
P.ellipse(14 - sw, 52, 12, 5.5, LEAF)
|
|
36
|
+
P.ellipse(42 + sw, 52, 12, 5.5, LEAF)
|
|
37
|
+
P.ellipse(28 + sw, 22, 24, 20, c.base, shine=True)
|
|
38
|
+
for x, y, r in ((14, 15, 3.4), (30, 8, 3.8), (43, 15, 3.0)):
|
|
39
|
+
P.ellipse(x + sw, y, r, r, WHITE, shade=False, top=True)
|
|
40
|
+
my = 25
|
|
41
|
+
P.ellipse(28 + sw, my, 17, gape / 2 + 2.4, MOUTH, shade=False, top=True)
|
|
42
|
+
if gape > 6:
|
|
43
|
+
P.ellipse(28 + sw, my + gape / 2, 9, max(2, gape / 3), TONGUE, shade=False, top=True)
|
|
44
|
+
for x in range(14, 40, 6):
|
|
45
|
+
P.poly([(x + sw, my - gape / 2 - 2), (x + 3 + sw, my - gape / 2 + 3.5), (x + 6 + sw, my - gape / 2 - 2)], WHITE, shade=False, top=True)
|
|
46
|
+
P.poly([(x + 2 + sw, my + gape / 2 + 3), (x + 5 + sw, my + gape / 2 - 3.5), (x + 8 + sw, my + gape / 2 + 3)], WHITE, shade=False, top=True)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def draw_mushroom(P: Painter, p: Pose) -> None:
|
|
50
|
+
c = P.p
|
|
51
|
+
k = p.i % 4
|
|
52
|
+
bob = {"idle": (0, 2)[p.i % 2], "run": (0, 2, 0, 2)[k]}.get(p.anim, 0)
|
|
53
|
+
hurt = p.anim == "hurt"
|
|
54
|
+
foot = (0, 2, 0, -2)[k] if p.anim == "run" else 0
|
|
55
|
+
P.ellipse(18 + foot, 51, 6.5, 4, _shade(c.base, 0.5))
|
|
56
|
+
P.ellipse(38 - foot, 51, 6.5, 4, _shade(c.base, 0.5))
|
|
57
|
+
P.rrect(20, 30 + bob, 37, 51, 6, STEM)
|
|
58
|
+
P.poly(_top_half(28, 32 + bob, 26, 20) + [(2, 33 + bob), (54, 33 + bob)], c.base, top=True)
|
|
59
|
+
P.ellipse(20, 17 + bob, 11, 6, c.light, shade=False)
|
|
60
|
+
for x, y, r in ((14, 19, 4.4), (30, 11, 4.8), (42, 20, 4), (24, 26, 2.8)):
|
|
61
|
+
P.ellipse(x, y + bob, r, r, WHITE, shade=False, top=True)
|
|
62
|
+
if hurt:
|
|
63
|
+
P.eyes([(22, 38 + bob), (33, 38 + bob)], r=3.2, hurt=True)
|
|
64
|
+
else:
|
|
65
|
+
P.eyes([(22, 38 + bob), (33, 38 + bob)], r=3.2)
|
|
66
|
+
P.line([(26, 45 + bob), (30, 45 + bob)], 1.6, EYE)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def draw_reaper(P: Painter, p: Pose) -> None:
|
|
70
|
+
c = P.p
|
|
71
|
+
k = p.i % 4
|
|
72
|
+
bob = (0, 2, 2, 0)[k] if p.anim in ("idle", "run") else 0
|
|
73
|
+
P.line([(48, 12 + bob), (48, 60 + bob)], 4, WOOD, top=False)
|
|
74
|
+
P.poly([(48, 6 + bob), (28, 4 + bob), (16, 14 + bob), (28, 10 + bob), (48, 16 + bob)], STEEL, top=True)
|
|
75
|
+
P.poly([(14, 34 + bob), (40, 34 + bob), (48, 62 + bob), (6, 62 + bob)], CLOAK)
|
|
76
|
+
P.ellipse(28, 21 + bob, 19, 18, CLOAK, shine=True)
|
|
77
|
+
P.ellipse(26, 22 + bob, 13, 13, _shade(CLOAK, 0.35), shade=False, top=True)
|
|
78
|
+
P.ellipse(26, 24 + bob, 9.4, 9.8, BONE, shade=False, top=True)
|
|
79
|
+
for ex in (21, 29):
|
|
80
|
+
P.ellipse(ex, 23 + bob, 2.8, 3.4, EYE, shade=False, top=True)
|
|
81
|
+
if p.anim != "hurt":
|
|
82
|
+
P.ellipse(ex, 23 + bob, 1.1, 1.1, GLOW, shade=False, top=True)
|
|
83
|
+
P.line([(22, 31 + bob), (30, 31 + bob)], 1.6, EYE)
|
|
84
|
+
P.ellipse(42, 40 + bob, 4.6, 4.6, BONE, shade=False, top=True)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def draw_flame(P: Painter, p: Pose) -> None:
|
|
88
|
+
c = P.p
|
|
89
|
+
k = p.i % 4
|
|
90
|
+
f = (0, 2, 0, -2)[k]
|
|
91
|
+
bob = (0, 2)[p.i % 2] if p.anim == "idle" else 0
|
|
92
|
+
P.poly([(24 + f, 0 + bob), (34, 16 + bob), (44, 28), (42, 42), (34, 52), (14, 52), (6, 42), (4, 28), (14, 16 + bob)], c.base, top=True)
|
|
93
|
+
P.poly([(6 - f, 18 + bob), (12, 28), (6, 36)], c.base)
|
|
94
|
+
P.poly([(42 + f, 16 + bob), (38, 28), (44, 36)], c.base)
|
|
95
|
+
P.poly([(24 - f, 16 + bob), (34, 30), (36, 42), (28, 50), (18, 50), (12, 42), (14, 30)], FLAME_MID, shade=False, top=True)
|
|
96
|
+
P.poly([(24, 28), (30, 38), (28, 48), (20, 48), (18, 38)], FLAME_CORE, shade=False, top=True)
|
|
97
|
+
for ex in (15, 29):
|
|
98
|
+
P.ellipse(ex + 2, 33 + bob, 3.4, 3.4, WHITE, shade=False, top=True)
|
|
99
|
+
P.ellipse(ex + 3, 34 + bob, 1.7, 1.9, EYE, shade=False, top=True)
|
|
100
|
+
if p.anim != "hurt":
|
|
101
|
+
P.line([(12, 26 + bob), (20, 30 + bob)], 1.8, EYE)
|
|
102
|
+
P.line([(36, 26 + bob), (28, 30 + bob)], 1.8, EYE)
|
|
103
|
+
P.line([(18, 43), (24, 44), (30, 43)], 2.0, EYE)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def draw_snail(P: Painter, p: Pose) -> None:
|
|
107
|
+
c = P.p
|
|
108
|
+
k = p.i % 4
|
|
109
|
+
st = (0, 2, 4, 2)[k] if p.anim == "run" else 0
|
|
110
|
+
bob = (0, 2)[p.i % 2] if p.anim == "idle" else 0
|
|
111
|
+
P.ellipse(30 + st / 2, 40, 26 + st / 2, 8.4, SNAIL_BODY)
|
|
112
|
+
P.ellipse(54 + st, 34 + bob, 9, 9, SNAIL_BODY, shine=True)
|
|
113
|
+
for sx in (50, 58):
|
|
114
|
+
x = sx + st
|
|
115
|
+
P.line([(x, 28 + bob), (x - 2, 14 + bob)], 4, SNAIL_BODY, top=False)
|
|
116
|
+
P.ellipse(x - 2, 12 + bob, 4.6, 4.6, WHITE, shade=False, top=True)
|
|
117
|
+
P.ellipse(x - 1, 12 + bob, 2.2, 2.6, EYE, shade=False, top=True)
|
|
118
|
+
P.line([(56 + st, 40 + bob), (60 + st, 40 + bob)], 1.6, EYE)
|
|
119
|
+
P.ellipse(24, 24 + bob, 20, 19, c.base, shine=True)
|
|
120
|
+
for r, col in ((16, _shade(c.base, 0.6)), (13, c.base), (9.6, _shade(c.base, 0.6)), (6.4, c.base), (3, _shade(c.base, 0.6))):
|
|
121
|
+
P.ellipse(24, 24 + bob, r, r, col, shade=False, top=True)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def draw_alien(P: Painter, p: Pose) -> None:
|
|
125
|
+
c = P.p
|
|
126
|
+
k = p.i % 4
|
|
127
|
+
bob = {"idle": (0, 2)[p.i % 2], "run": (0, 2, 0, 2)[k]}.get(p.anim, 0)
|
|
128
|
+
sw = ((-4, 4), (0, 0), (4, -4), (0, 0))[k] if p.anim == "run" else (0, 0)
|
|
129
|
+
hurt = p.anim == "hurt"
|
|
130
|
+
P.line([(24, 8 + bob), (18, 0 + bob)], 2.4, c.shadow)
|
|
131
|
+
P.line([(32, 8 + bob), (38, 0 + bob)], 2.4, c.shadow)
|
|
132
|
+
P.ellipse(18, 1 + bob, 3, 3, c.light, shade=False, top=True)
|
|
133
|
+
P.ellipse(38, 1 + bob, 3, 3, c.light, shade=False, top=True)
|
|
134
|
+
P.rrect(18 + sw[0], 52, 24 + sw[0], 62, 2.4, c.shadow)
|
|
135
|
+
P.rrect(30 + sw[1], 52, 36 + sw[1], 62, 2.4, c.base)
|
|
136
|
+
P.ellipse(27, 48 + bob, 12, 10, c.base, shine=True)
|
|
137
|
+
P.line([(14, 44 + bob), (6, 48 + bob)], 4.4, c.base, top=False)
|
|
138
|
+
P.line([(40, 44 + bob), (48, 48 + bob)], 4.4, c.shadow, top=False)
|
|
139
|
+
P.ellipse(28, 24 + bob, 23, 20, c.base, shine=True)
|
|
140
|
+
for ex in (17, 39):
|
|
141
|
+
if hurt:
|
|
142
|
+
P.line([(ex - 4, 20 + bob), (ex + 4, 28 + bob)], 2, EYE)
|
|
143
|
+
P.line([(ex + 4, 20 + bob), (ex - 4, 28 + bob)], 2, EYE)
|
|
144
|
+
else:
|
|
145
|
+
sgn = -1 if ex < 28 else 1
|
|
146
|
+
P.poly([(ex - 6.4, 20 + bob), (ex + 6.4, 23 + bob), (ex + 3 * sgn, 34 + bob), (ex - 3 * sgn, 34 + bob)], EYE, shade=False, top=True)
|
|
147
|
+
P.ellipse(ex - 2, 25 + bob, 1.6, 1.6, WHITE, shade=False, top=True)
|
|
148
|
+
P.line([(24, 37 + bob), (32, 37 + bob)], 1.8, _shade(c.base, 0.4))
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
PAINTERS = {"plant": draw_plant, "mushroom": draw_mushroom, "reaper": draw_reaper, "flame": draw_flame,
|
|
152
|
+
"snail": draw_snail, "alien": draw_alien}
|
|
@@ -22,6 +22,8 @@ CAST: list[tuple[str, str, str]] = [
|
|
|
22
22
|
("Robot", "robot", "#83769c"), ("Turret", "turret", "#83769c"), ("Beetle", "bug", "#ab5236"),
|
|
23
23
|
("Wolf", "beast", "#8a8a9a"), ("Spider", "spider", "#5f574f"), ("Dragon", "dragon", "#ff004d"),
|
|
24
24
|
("Ghost", "ghost", "#c2c3c7"), ("Snake", "snake", "#3a8a40"),
|
|
25
|
+
("BitingPlant", "plant", "#ff004d"), ("Mushroom", "mushroom", "#e8403a"), ("Reaper", "reaper", "#7e2553"),
|
|
26
|
+
("Flame", "flame", "#ff6a1a"), ("Snail", "snail", "#b06ad8"), ("Alien", "alien", "#6fd24a"),
|
|
25
27
|
("Coin", "coin", "#ffcc40"), ("Star", "star", "#ffec27"), ("Gem", "gem", "#29adff"),
|
|
26
28
|
("Heart", "heart", "#ff004d"), ("Orb", "orb", "#83769c"), ("GoldKey", "key", "#ffcc40"),
|
|
27
29
|
("HealthPotion", "potion", "#e04858"), ("MagicScroll", "scroll", "#c03040"), ("Bomb", "bomb", "#333344"),
|
|
@@ -65,3 +67,42 @@ def gallery(style: str = "fine", columns: int = 8, cast: list[tuple[str, str, st
|
|
|
65
67
|
else y + (CELL - size[1]) // 2))
|
|
66
68
|
d.text((x + 2, y + CELL + 1), name[:15], fill=TEXT)
|
|
67
69
|
return out
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
PARADES: dict[str, list[str]] = {
|
|
73
|
+
"heroes": ["Knight", "Wizard", "Ranger", "Viking", "King", "Princess", "Ninja", "Hero", "Skeleton", "SkeletonArcher",
|
|
74
|
+
"Zombie", "Orc", "Goblin", "Alien", "Reaper"],
|
|
75
|
+
"monsters": ["Slime", "Frog", "BitingPlant", "Wolf", "Mushroom", "Snail", "Flame", "Ghost", "Snake", "Spider", "Bat",
|
|
76
|
+
"Dragon", "Robot", "Beetle", "Turret"],
|
|
77
|
+
"items": ["Coin", "Star", "Gem", "Heart", "GoldKey", "HealthPotion", "MagicScroll", "Bomb", "TreasureChest", "Arrow",
|
|
78
|
+
"Orb", "BerryBush", "Tree", "PineTree", "MossyRock"],
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def parade(which: str = "heroes", columns: int = 5, cell: int = 128, fps: int = 10, seconds: float = 3.0,
|
|
83
|
+
style: str = "fine") -> list[Image.Image]:
|
|
84
|
+
"""An animated sheet: every cast member idles for half the time and runs for the other half (where it can),
|
|
85
|
+
each at the largest whole-number scale that fits its cell. Returns the GIF frames."""
|
|
86
|
+
by_name = {c[0]: c for c in CAST}
|
|
87
|
+
cast = [by_name[n] for n in PARADES[which]]
|
|
88
|
+
sprites = [render(name, kind, color, style) for name, kind, color in cast]
|
|
89
|
+
rows = -(-len(cast) // columns)
|
|
90
|
+
steps = round(fps * seconds)
|
|
91
|
+
out = []
|
|
92
|
+
for step in range(steps):
|
|
93
|
+
t = step / fps
|
|
94
|
+
img = Image.new("RGBA", (PAD + columns * (cell + PAD), PAD + rows * (cell + PAD)), BG)
|
|
95
|
+
d = ImageDraw.Draw(img)
|
|
96
|
+
for n, ((name, kind, _color), s) in enumerate(zip(cast, sprites)):
|
|
97
|
+
anim = "run" if "run" in s.animations and t >= seconds / 2 else "idle"
|
|
98
|
+
a = s.animations.get(anim) or next(iter(s.animations.values()))
|
|
99
|
+
w, h = s.frame_size
|
|
100
|
+
zoom = max(1, cell // max(w, h))
|
|
101
|
+
idx = int(t * a.fps) % len(a.frames)
|
|
102
|
+
frame = Image.fromarray(s.frame(*a.frames[idx])).resize((w * zoom, h * zoom), Image.NEAREST)
|
|
103
|
+
x, y = PAD + (n % columns) * (cell + PAD), PAD + (n // columns) * (cell + PAD)
|
|
104
|
+
d.rectangle([x, y, x + cell - 1, y + cell - 1], fill=TILE)
|
|
105
|
+
bottom = KINDS[kind].anchor == "bottom"
|
|
106
|
+
img.alpha_composite(frame, (x + (cell - w * zoom) // 2, y + cell - h * zoom - 6 if bottom else y + (cell - h * zoom) // 2))
|
|
107
|
+
out.append(img.convert("RGB"))
|
|
108
|
+
return out
|
|
@@ -16,7 +16,7 @@ from typing import Callable
|
|
|
16
16
|
|
|
17
17
|
import numpy as np
|
|
18
18
|
|
|
19
|
-
from . import chibi, creatures, items, motion, palettes
|
|
19
|
+
from . import chibi, creatures, items, monsters, motion, palettes
|
|
20
20
|
from .spriteset import SpriteSet, build_sheet, finalize
|
|
21
21
|
|
|
22
22
|
RGBA = tuple[int, int, int, int]
|
|
@@ -765,6 +765,16 @@ KINDS: dict[str, Kind] = {
|
|
|
765
765
|
"ghost": Kind((12, 14), {"idle": (4, 6, True), "run": (4, 8, True), "hurt": (1, 1, False)}, None),
|
|
766
766
|
"snake": Kind((16, 8), {"idle": (2, 3, True), "run": (4, 8, True), "jump": (1, 1, False), "fall": (1, 1, False),
|
|
767
767
|
"hurt": (1, 1, False)}, None, "bottom"),
|
|
768
|
+
# more chibi monsters, painted natively at the fine size by monsters.py
|
|
769
|
+
"plant": Kind((14, 16), {"idle": (4, 6, True), "run": (4, 8, True), "hurt": (1, 1, False)}, None, "bottom"),
|
|
770
|
+
"mushroom": Kind((14, 14), {"idle": (2, 3, True), "run": (4, 8, True), "jump": (1, 1, False), "fall": (1, 1, False),
|
|
771
|
+
"hurt": (1, 1, False)}, None, "bottom"),
|
|
772
|
+
"reaper": Kind((14, 16), {"idle": (4, 5, True), "run": (4, 8, True), "hurt": (1, 1, False)}, None),
|
|
773
|
+
"flame": Kind((12, 14), {"idle": (4, 8, True), "run": (4, 10, True), "hurt": (1, 1, False)}, None, "bottom"),
|
|
774
|
+
"snail": Kind((16, 12), {"idle": (2, 3, True), "run": (4, 5, True), "jump": (1, 1, False), "fall": (1, 1, False),
|
|
775
|
+
"hurt": (1, 1, False)}, None, "bottom"),
|
|
776
|
+
"alien": Kind((12, 16), {"idle": (2, 3, True), "run": (4, 10, True), "jump": (1, 1, False), "fall": (1, 1, False),
|
|
777
|
+
"hurt": (1, 1, False)}, None, "bottom"),
|
|
768
778
|
# chibi items and decorative props, painted natively by items.py
|
|
769
779
|
"key": Kind((8, 8), {"idle": (4, 6, True)}, None),
|
|
770
780
|
"potion": Kind((8, 10), {"idle": (4, 6, True)}, None),
|
|
@@ -814,7 +824,7 @@ def refine(f: np.ndarray, outline: RGBA) -> np.ndarray:
|
|
|
814
824
|
|
|
815
825
|
# Animations a painter draws itself (on top of the kind's own), and the kinds that get a derived attack pulse.
|
|
816
826
|
DRAWN_EXTRA: dict[str, dict[str, tuple[int, float, bool]]] = {"hero": {"attack": (3, 14, False)}}
|
|
817
|
-
NATIVE = {**creatures.PAINTERS, **items.PAINTERS} # kinds painted at the fine size (coarse = 2:1 shrink)
|
|
827
|
+
NATIVE = {**creatures.PAINTERS, **items.PAINTERS, **monsters.PAINTERS} # kinds painted at the fine size (coarse = 2:1 shrink)
|
|
818
828
|
ATTACKERS = {"hero", "slime", "hopper", "flyer", "robot", "bug", "beast", "spider", "dragon", "ghost", "snake"}
|
|
819
829
|
DERIVED = {"attack": (3, 14.0, False), "die": (5, 10.0, False)}
|
|
820
830
|
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""More chibi monsters in the creatures.py language (big faces, flat colour blocks, one shadow tone, a touch of
|
|
2
|
+
light, a dark outline), painted natively at the fine size: a biting plant, a mushroom, a reaper, a flame,
|
|
3
|
+
a snail and a little alien. The entity colour is the creature's main colour (cap, shell, flame, skin...).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from .chibi import EYE, WHITE, Fig, tone
|
|
11
|
+
from .creatures import FANG, Look, _eye
|
|
12
|
+
|
|
13
|
+
RGBA = tuple[int, int, int, int]
|
|
14
|
+
CLEAR: RGBA = (0, 0, 0, 0)
|
|
15
|
+
LEAF = "#58b848"
|
|
16
|
+
MOUTH = "#8a1a3a"
|
|
17
|
+
TONGUE = "#ff7a96"
|
|
18
|
+
STEM = "#f4e6c6"
|
|
19
|
+
CLOAK = "#3c3058"
|
|
20
|
+
BONE = "#f2ecd8"
|
|
21
|
+
GLOW = (255, 90, 90, 255)
|
|
22
|
+
FLAME_MID = "#ffb02e"
|
|
23
|
+
FLAME_CORE = "#fff0a0"
|
|
24
|
+
SNAIL_BODY = "#d8c4cc"
|
|
25
|
+
WOOD = "#8a5a34"
|
|
26
|
+
STEEL = "#c4ccdc"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _clip(g: Fig, cx: float, cy: float, rx: float, ry: float, c: RGBA, y0: float = -99, y1: float = 99) -> None:
|
|
30
|
+
"""An ellipse restricted to the rows y0..y1 (a cap with a flat underside, a head with a cut-out mouth)."""
|
|
31
|
+
ys, xs = np.mgrid[0:g.h, 0:g.w]
|
|
32
|
+
m = ((xs + 0.5 - cx) / rx) ** 2 + ((ys + 0.5 - cy) / ry) ** 2 <= 1.0
|
|
33
|
+
m &= (ys >= y0) & (ys <= y1)
|
|
34
|
+
g.px[m] = c
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _tri(g: Fig, x: float, y: float, w: float, h: float, c: RGBA) -> None:
|
|
38
|
+
g.poly([(x, y), (x + w / 2, y + h), (x + w, y)] if h > 0 else [(x, y), (x + w / 2, y + h), (x + w, y)], c)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# --- a biting plant ----------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
def paint_plant(g: Fig, L: Look, anim: str, i: int) -> None:
|
|
44
|
+
leaf, cap = tone(LEAF, None), L.body
|
|
45
|
+
k = i % 4
|
|
46
|
+
sway = (0, 1, 0, -1)[k]
|
|
47
|
+
hurt = anim == "hurt"
|
|
48
|
+
gape = 1 if hurt else {"idle": (3, 5, 7, 4)[k], "run": (5, 2, 5, 2)[k]}.get(anim, 4)
|
|
49
|
+
cx, cy = 14 + sway, 11
|
|
50
|
+
g.line(14, 31, 14 + sway, 19, leaf.base, 4) # stem
|
|
51
|
+
g.line(16, 30, 16 + sway, 19, leaf.shadow, 1)
|
|
52
|
+
g.ellipse(7 - sway, 26, 5.5, 2.6, leaf.base) # leaves
|
|
53
|
+
g.ellipse(21 + sway, 26, 5.5, 2.6, leaf.base)
|
|
54
|
+
g.rect(4, 26, 9, 26, leaf.light)
|
|
55
|
+
g.ellipse(cx, cy, 11, 9.5, cap.base) # head
|
|
56
|
+
g.ellipse(cx - 3, cy - 4, 5, 3, cap.light)
|
|
57
|
+
g.ellipse(cx + 5, cy + 3, 5.5, 5, cap.shadow)
|
|
58
|
+
for sx, sy, r in ((cx - 7, cy - 3, 1.6), (cx + 1, cy - 7, 1.9), (cx + 7, cy - 3, 1.5)):
|
|
59
|
+
g.ellipse(sx, sy, r, r, WHITE) # spots
|
|
60
|
+
my = cy + 1 # the mouth
|
|
61
|
+
_clip(g, cx, my, 8.5, gape / 2 + 1.2, tone(MOUTH, None).base, 0, 99)
|
|
62
|
+
g.ellipse(cx, my + gape / 2, 4.6, max(1.0, gape / 3), tone(TONGUE, None).base) if gape > 3 else None
|
|
63
|
+
for x in range(int(cx - 7), int(cx + 7), 3): # fangs
|
|
64
|
+
_tri(g, x, my - gape / 2 - 1, 3, 2.5, FANG)
|
|
65
|
+
g.poly([(x + 1, my + gape / 2 + 1.4), (x + 2.5, my + gape / 2 - 1.6), (x + 4, my + gape / 2 + 1.4)], FANG)
|
|
66
|
+
if hurt:
|
|
67
|
+
g.set(cx - 5, cy - 4, EYE)
|
|
68
|
+
g.set(cx + 5, cy - 4, EYE)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# --- a mushroom --------------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
def paint_mushroom(g: Fig, L: Look, anim: str, i: int) -> None:
|
|
74
|
+
cap, stem = L.body, tone(STEM, None)
|
|
75
|
+
k = i % 4
|
|
76
|
+
bob = {"idle": (0, 1)[i % 2], "run": (0, 1, 0, 1)[k]}.get(anim, 0)
|
|
77
|
+
hurt = anim == "hurt"
|
|
78
|
+
foot = (0, 1, 0, -1)[k] if anim == "run" else 0
|
|
79
|
+
g.ellipse(9, 25.5, 3.2, 2, L.dark.base) # feet
|
|
80
|
+
g.ellipse(19, 25.5, 3.2, 2, L.dark.base)
|
|
81
|
+
g.rect(7 + foot, 25, 9 + foot, 26, L.dark.shadow)
|
|
82
|
+
g.rect(10, 15 + bob, 18, 25, stem.base) # stem
|
|
83
|
+
g.rect(16, 16 + bob, 18, 25, stem.shadow)
|
|
84
|
+
g.rect(9, 20 + bob, 9, 24, stem.base)
|
|
85
|
+
g.rect(19, 20 + bob, 19, 24, stem.shadow)
|
|
86
|
+
_clip(g, 14, 11 + bob, 13, 9.5, cap.base, 0, 16 + bob) # cap, flat underneath
|
|
87
|
+
_clip(g, 10, 8 + bob, 6, 3.4, cap.light, 0, 16 + bob)
|
|
88
|
+
_clip(g, 20, 14 + bob, 6, 3, cap.shadow, 12 + bob, 16 + bob)
|
|
89
|
+
for sx, sy, r in ((7, 9, 2.2), (15, 5.5, 2.4), (21, 10, 2), (12, 13, 1.4)):
|
|
90
|
+
g.ellipse(sx, sy + bob, r, r, WHITE)
|
|
91
|
+
g.rect(4, 15 + bob, 24, 16 + bob, cap.shadow) # the rim
|
|
92
|
+
_eye(g, 11, 18 + bob, closed=(anim == "idle" and i == 1), hurt=hurt)
|
|
93
|
+
_eye(g, 15, 18 + bob, closed=(anim == "idle" and i == 1), hurt=hurt)
|
|
94
|
+
g.rect(13, 22 + bob, 15, 22 + bob, EYE) # tiny mouth
|
|
95
|
+
g.set(10, 21 + bob, tone("#f0a0a0", None).base) # blush
|
|
96
|
+
g.set(17, 21 + bob, tone("#f0a0a0", None).base)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# --- a reaper ----------------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
def paint_reaper(g: Fig, L: Look, anim: str, i: int) -> None:
|
|
102
|
+
robe, bone = tone(CLOAK, None), tone(BONE, None)
|
|
103
|
+
k = i % 4
|
|
104
|
+
bob = (0, 1, 1, 0)[k] if anim in ("idle", "run") else 0
|
|
105
|
+
hurt = anim == "hurt"
|
|
106
|
+
wave = (0, 1, 0, -1)[k]
|
|
107
|
+
pole = tone(WOOD, None)
|
|
108
|
+
g.line(24, 6 + bob, 24, 30 + bob, pole.base, 2) # the scythe
|
|
109
|
+
g.poly([(24, 3 + bob), (14, 2 + bob), (8, 7 + bob), (14, 5 + bob), (24, 8 + bob)], tone(STEEL, None).base)
|
|
110
|
+
g.poly([(24, 3 + bob), (14, 2 + bob), (10, 5 + bob), (24, 5 + bob)], tone(STEEL, None).light)
|
|
111
|
+
g.poly([(8, 16 + bob), (20, 16 + bob), (24, 30 + bob), (3, 30 + bob)], robe.base) # robe
|
|
112
|
+
g.poly([(15, 16 + bob), (20, 16 + bob), (24, 30 + bob), (16, 30 + bob)], robe.shadow)
|
|
113
|
+
for x in range(3, 24, 4): # ragged hem
|
|
114
|
+
g.poly([(x, 29 + bob), (x + 2 + wave, 32), (x + 4, 29 + bob)], robe.base)
|
|
115
|
+
g.ellipse(14, 10 + bob, 9.5, 9, robe.base) # hood
|
|
116
|
+
g.ellipse(17, 12 + bob, 6, 6, robe.shadow)
|
|
117
|
+
g.ellipse(13, 11 + bob, 6.6, 6.4, tone("#14101e", None).base)
|
|
118
|
+
g.ellipse(13, 12 + bob, 4.6, 4.8, bone.base) # skull
|
|
119
|
+
g.rect(9, 12 + bob, 10, 12 + bob, bone.light)
|
|
120
|
+
for ex in (10, 14): # eye sockets with a red glint
|
|
121
|
+
g.rect(ex, 11 + bob, ex + 1, 13 + bob, EYE)
|
|
122
|
+
if not hurt:
|
|
123
|
+
g.set(ex + 1, 12 + bob, GLOW)
|
|
124
|
+
g.rect(11, 15 + bob, 15, 15 + bob, EYE) # teeth line
|
|
125
|
+
for tx in (12, 14):
|
|
126
|
+
g.set(tx, 15 + bob, bone.base)
|
|
127
|
+
g.ellipse(21, 20 + bob, 2.4, 2.4, bone.base) # bony hand on the pole
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# --- a flame spirit ----------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
def paint_flame(g: Fig, L: Look, anim: str, i: int) -> None:
|
|
133
|
+
outer, mid, core = L.body, tone(FLAME_MID, None), tone(FLAME_CORE, None)
|
|
134
|
+
k = i % 4
|
|
135
|
+
f = (0, 1, 0, -1)[k]
|
|
136
|
+
bob = (0, 1)[i % 2] if anim == "idle" else 0
|
|
137
|
+
hurt = anim == "hurt"
|
|
138
|
+
g.poly([(12 + f, 0 + bob), (17, 8 + bob), (22, 14), (21, 21), (17, 26), (7, 26), (3, 21), (2, 14), (7, 8 + bob)], outer.base)
|
|
139
|
+
g.poly([(3 - f, 9 + bob), (6, 14), (3, 18)], outer.base) # side tongues
|
|
140
|
+
g.poly([(21 + f, 8 + bob), (19, 14), (22, 18)], outer.base)
|
|
141
|
+
g.poly([(12 - f, 8 + bob), (17, 15), (18, 21), (14, 25), (9, 25), (6, 21), (7, 15)], mid.base)
|
|
142
|
+
g.poly([(12, 14), (15, 19), (14, 24), (10, 24), (9, 19)], core.base)
|
|
143
|
+
g.rect(5, 17, 6, 22, outer.shadow)
|
|
144
|
+
angry = not hurt
|
|
145
|
+
for ex in (7, 14): # slanted angry eyes
|
|
146
|
+
g.rect(ex, 15 + bob, ex + 2, 17 + bob, WHITE)
|
|
147
|
+
g.rect(ex + 1, 16 + bob, ex + 2, 17 + bob, EYE)
|
|
148
|
+
if angry:
|
|
149
|
+
g.line(6, 13 + bob, 10, 15 + bob, EYE, 1)
|
|
150
|
+
g.line(17, 13 + bob, 13, 15 + bob, EYE, 1)
|
|
151
|
+
g.rect(9, 21, 14, 22, EYE) # jagged grin
|
|
152
|
+
for tx in (9, 11, 13):
|
|
153
|
+
g.set(tx, 21, WHITE)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# --- a snail -----------------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
def paint_snail(g: Fig, L: Look, anim: str, i: int) -> None:
|
|
159
|
+
shell, body = L.body, tone(SNAIL_BODY, None)
|
|
160
|
+
k = i % 4
|
|
161
|
+
stretch = (0, 1, 2, 1)[k] if anim == "run" else 0
|
|
162
|
+
bob = (0, 1)[i % 2] if anim == "idle" else 0
|
|
163
|
+
hurt = anim == "hurt"
|
|
164
|
+
g.ellipse(13 + stretch / 2, 20, 12 + stretch / 2, 4.2, body.base) # the foot
|
|
165
|
+
g.rect(3, 22, 23 + stretch, 23, body.shadow)
|
|
166
|
+
g.ellipse(25 + stretch, 17 + bob, 4.5, 4.5, body.base) # head
|
|
167
|
+
g.ellipse(24 + stretch, 15 + bob, 2.5, 1.8, body.light)
|
|
168
|
+
for sx, wig in ((23, 0), (27, 1)): # eye stalks
|
|
169
|
+
x = sx + stretch
|
|
170
|
+
g.line(x, 14 + bob, x - 1 + (wig if k % 2 else 0), 7 + bob, body.base, 2)
|
|
171
|
+
g.ellipse(x - 1 + (wig if k % 2 else 0), 6 + bob, 2.3, 2.3, WHITE)
|
|
172
|
+
g.rect(x - 1 + (wig if k % 2 else 0), 5 + bob, x + (wig if k % 2 else 0), 7 + bob, EYE) if not hurt else g.set(x - 1, 6 + bob, EYE)
|
|
173
|
+
g.rect(26 + stretch, 20 + bob, 28 + stretch, 20 + bob, EYE) # smile
|
|
174
|
+
g.ellipse(11, 12 + bob, 10, 9.5, shell.base) # shell with a spiral
|
|
175
|
+
g.ellipse(8, 8 + bob, 5, 3.4, shell.light)
|
|
176
|
+
g.ellipse(14, 15 + bob, 6.5, 5, shell.shadow)
|
|
177
|
+
for r, c in ((8.2, L.dark.base), (6.4, shell.base), (4.6, L.dark.base), (3.0, shell.base), (1.4, L.dark.base)):
|
|
178
|
+
_clip(g, 11, 12 + bob, r, r, c, 5 + bob, 23)
|
|
179
|
+
g.rect(1, 19, 5, 19, body.base) # tail end
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# --- a little alien ----------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
ALIEN = "#6fd24a"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def paint_alien(g: Fig, L: Look, anim: str, i: int) -> None:
|
|
188
|
+
sk = L.body
|
|
189
|
+
k = i % 4
|
|
190
|
+
bob = {"idle": (0, 1)[i % 2], "run": (0, 1, 0, 1)[k]}.get(anim, 0)
|
|
191
|
+
swing = ((-2, 2), (0, 0), (2, -2), (0, 0))[k] if anim == "run" else (0, 0)
|
|
192
|
+
hurt = anim == "hurt"
|
|
193
|
+
g.line(12, 4 + bob, 9, 0 + bob, sk.shadow, 1) # antennae
|
|
194
|
+
g.line(16, 4 + bob, 19, 0 + bob, sk.shadow, 1)
|
|
195
|
+
g.ellipse(9, 0.5 + bob, 1.4, 1.4, sk.light)
|
|
196
|
+
g.ellipse(19, 0.5 + bob, 1.4, 1.4, sk.light)
|
|
197
|
+
g.rect(9 + swing[0], 26, 11 + swing[0], 30, sk.shadow) # legs
|
|
198
|
+
g.rect(15 + swing[1], 26, 17 + swing[1], 30, sk.base)
|
|
199
|
+
g.rect(8 + swing[0], 30, 12 + swing[0], 31, L.dark.base)
|
|
200
|
+
g.rect(14 + swing[1], 30, 18 + swing[1], 31, L.dark.base)
|
|
201
|
+
g.ellipse(13.5, 24 + bob, 6, 5, sk.base) # body
|
|
202
|
+
g.ellipse(11.5, 23 + bob, 3, 2.5, sk.light)
|
|
203
|
+
g.line(7, 22 + bob, 3, 24 + bob + swing[0] // 2, sk.base, 2) # arms
|
|
204
|
+
g.line(20, 22 + bob, 24, 24 + bob + swing[1] // 2, sk.shadow, 2)
|
|
205
|
+
g.ellipse(14, 12 + bob, 11.5, 10, sk.base) # the big head
|
|
206
|
+
g.ellipse(10, 7 + bob, 6, 3.4, sk.light)
|
|
207
|
+
g.ellipse(18.5, 14 + bob, 5, 6, sk.shadow)
|
|
208
|
+
for ex, flip in ((8.5, -1), (19.5, 1)): # big black almond eyes
|
|
209
|
+
if hurt:
|
|
210
|
+
g.line(ex - 2, 10 + bob, ex + 2, 14 + bob, EYE, 1)
|
|
211
|
+
g.line(ex + 2, 10 + bob, ex - 2, 14 + bob, EYE, 1)
|
|
212
|
+
else:
|
|
213
|
+
g.poly([(ex - 3.2, 10 + bob), (ex + 3.2, 11.5 + bob + flip * 0.0), (ex + 1.5 * flip, 17 + bob), (ex - 1.5 * flip, 17 + bob)],
|
|
214
|
+
EYE)
|
|
215
|
+
g.set(ex - 1, 12 + bob, WHITE)
|
|
216
|
+
g.rect(12, 18 + bob, 15, 18 + bob, L.dark.base) # mouth
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
PAINTERS = {"plant": paint_plant, "mushroom": paint_mushroom, "reaper": paint_reaper, "flame": paint_flame,
|
|
220
|
+
"snail": paint_snail, "alien": paint_alien}
|