spawnpoint-studio 0.2.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. {spawnpoint_studio-0.2.0/compiler/spawnpoint_studio.egg-info → spawnpoint_studio-0.3.1}/PKG-INFO +42 -16
  2. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/README.md +41 -15
  3. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/analyze.py +63 -7
  4. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/build.py +2 -1
  5. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/cli.py +2 -1
  6. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/demo.py +40 -13
  7. spawnpoint_studio-0.3.1/compiler/spawnpoint/demo_touch.js +118 -0
  8. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/gif.py +10 -7
  9. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/graph.py +8 -0
  10. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/llm_server.py +5 -2
  11. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/project.py +3 -1
  12. spawnpoint_studio-0.3.1/compiler/spawnpoint/sprites/cartoon.py +477 -0
  13. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/sprites/generate.py +195 -14
  14. spawnpoint_studio-0.3.1/compiler/spawnpoint/sprites/motion.py +72 -0
  15. spawnpoint_studio-0.3.1/compiler/spawnpoint/sprites/palettes.py +111 -0
  16. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/sprites/resolve.py +17 -4
  17. spawnpoint_studio-0.3.1/compiler/spawnpoint/sprites/scenery.py +471 -0
  18. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/sprites/spriteset.py +7 -5
  19. spawnpoint_studio-0.3.1/compiler/spawnpoint/sprites/tiles.py +362 -0
  20. spawnpoint_studio-0.3.1/compiler/spawnpoint/studio/assemble.py +1127 -0
  21. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/brief.py +123 -26
  22. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/edits.py +28 -0
  23. spawnpoint_studio-0.3.1/compiler/spawnpoint/studio/genres.py +248 -0
  24. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/levelgen.py +99 -0
  25. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/orchestrator.py +31 -15
  26. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/prompts.py +50 -4
  27. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/webcheck.py +7 -3
  28. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio_cli.py +10 -1
  29. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1/compiler/spawnpoint_studio.egg-info}/PKG-INFO +42 -16
  30. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint_studio.egg-info/SOURCES.txt +25 -0
  31. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/conftest.py +7 -0
  32. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_demo.py +2 -1
  33. spawnpoint_studio-0.3.1/compiler/tests/test_genres.py +207 -0
  34. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_live_llm.py +5 -0
  35. spawnpoint_studio-0.3.1/compiler/tests/test_sprites.py +537 -0
  36. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_studio.py +103 -9
  37. spawnpoint_studio-0.3.1/examples/castle_defense.json +341 -0
  38. spawnpoint_studio-0.3.1/examples/knight_quest.json +348 -0
  39. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/examples/night_survivor.json +42 -22
  40. spawnpoint_studio-0.3.1/examples/star_run.json +444 -0
  41. spawnpoint_studio-0.3.1/examples/sunny_farm.json +223 -0
  42. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/animator_component.gd +18 -2
  43. spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/base_component.gd +31 -0
  44. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/bubble_board_component.gd +16 -2
  45. spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/crop_pest_component.gd +77 -0
  46. spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/farm_plot_component.gd +159 -0
  47. spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/farmer_component.gd +158 -0
  48. spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/flight_path_component.gd +101 -0
  49. spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/ground_patch_component.gd +37 -0
  50. spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/health_bar_component.gd +41 -0
  51. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/juice_component.gd +36 -2
  52. spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/market_component.gd +52 -0
  53. spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/path_follower_component.gd +95 -0
  54. spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/shmup_ship_component.gd +65 -0
  55. spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/tower_brain_component.gd +39 -0
  56. spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/tower_builder_component.gd +105 -0
  57. spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/tower_slot_component.gd +47 -0
  58. spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/wave_spawner_component.gd +106 -0
  59. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/manifest.json +785 -16
  60. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/camera_rig.gd +3 -0
  61. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/entity_core.gd +2 -0
  62. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/fx.gd +95 -0
  63. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/hud.gd +4 -1
  64. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/level.gd +3 -0
  65. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/build.gd +256 -11
  66. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/playtest_nav.gd +3 -1
  67. spawnpoint_studio-0.3.1/godot/addons/spawn_point/tools/record.gd +233 -0
  68. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/pyproject.toml +2 -2
  69. spawnpoint_studio-0.2.0/compiler/spawnpoint/sprites/tiles.py +0 -102
  70. spawnpoint_studio-0.2.0/compiler/spawnpoint/studio/assemble.py +0 -570
  71. spawnpoint_studio-0.2.0/compiler/tests/test_sprites.py +0 -261
  72. spawnpoint_studio-0.2.0/godot/addons/spawn_point/tools/record.gd +0 -90
  73. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/LICENSE +0 -0
  74. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/MANIFEST.in +0 -0
  75. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/__init__.py +0 -0
  76. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/cockpit/__init__.py +0 -0
  77. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/cockpit/server.py +0 -0
  78. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/cockpit/static/app.css +0 -0
  79. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/cockpit/static/app.js +0 -0
  80. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/cockpit/static/index.html +0 -0
  81. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/diagnostics.py +0 -0
  82. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/expr.py +0 -0
  83. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/godot.py +0 -0
  84. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/level.py +0 -0
  85. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/manifest.py +0 -0
  86. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/playtest/__init__.py +0 -0
  87. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/playtest/report.py +0 -0
  88. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/playtest/runner.py +0 -0
  89. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/playtest/tuner.py +0 -0
  90. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/py.typed +0 -0
  91. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/rules.py +0 -0
  92. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/sfx.py +0 -0
  93. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/sprites/__init__.py +0 -0
  94. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/sprites/ai.py +0 -0
  95. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/sprites/contour.py +0 -0
  96. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/sprites/importer.py +0 -0
  97. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/__init__.py +0 -0
  98. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/creative.py +0 -0
  99. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/dataset.py +0 -0
  100. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/llm.py +0 -0
  101. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/web.py +0 -0
  102. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint_studio.egg-info/dependency_links.txt +0 -0
  103. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint_studio.egg-info/entry_points.txt +0 -0
  104. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint_studio.egg-info/requires.txt +0 -0
  105. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint_studio.egg-info/top_level.txt +0 -0
  106. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_ai_sprites.py +0 -0
  107. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_analyze.py +0 -0
  108. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_cockpit.py +0 -0
  109. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_e2e.py +0 -0
  110. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_expr.py +0 -0
  111. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_gif.py +0 -0
  112. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_godot_library.py +0 -0
  113. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_graph.py +0 -0
  114. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_level.py +0 -0
  115. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_manifest.py +0 -0
  116. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_playtest.py +0 -0
  117. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_rules.py +0 -0
  118. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_sfx.py +0 -0
  119. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_web.py +0 -0
  120. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/examples/coin_hopper.json +0 -0
  121. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/examples/dino_eggs.json +0 -0
  122. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/examples/neon_arena.json +0 -0
  123. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/examples/ninja_rescue.json +0 -0
  124. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/examples/pixel_arena.json +0 -0
  125. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/examples/pixel_hopper.json +0 -0
  126. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/aim_component.gd +0 -0
  127. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/chase_ai_component.gd +0 -0
  128. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/collectible_component.gd +0 -0
  129. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/experience_component.gd +0 -0
  130. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/gate_component.gd +0 -0
  131. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/health_component.gd +0 -0
  132. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/hitbox_component.gd +0 -0
  133. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/hurtbox_component.gd +0 -0
  134. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/level_up_menu_component.gd +0 -0
  135. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/patrol_ai_component.gd +0 -0
  136. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/platformer_movement_component.gd +0 -0
  137. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/player_input_component.gd +0 -0
  138. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/projectile_component.gd +0 -0
  139. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/spawner_component.gd +0 -0
  140. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/stomp_component.gd +0 -0
  141. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/top_down_movement_component.gd +0 -0
  142. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/trigger_zone_component.gd +0 -0
  143. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/weapon_component.gd +0 -0
  144. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/xp_orb_component.gd +0 -0
  145. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/cockpit.gd +0 -0
  146. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/game_state.gd +0 -0
  147. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/sfx.gd +0 -0
  148. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/sp.gd +0 -0
  149. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/world.gd +0 -0
  150. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/shaders/flash.gdshader +0 -0
  151. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/shaders/floor_grid.gdshader +0 -0
  152. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/build.tscn +0 -0
  153. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/capture.gd +0 -0
  154. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/capture.tscn +0 -0
  155. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/dump_manifest.gd +0 -0
  156. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/dump_manifest.tscn +0 -0
  157. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/playtest.gd +0 -0
  158. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/playtest.tscn +0 -0
  159. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/record.tscn +0 -0
  160. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/smoke.gd +0 -0
  161. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/smoke.tscn +0 -0
  162. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/setup.cfg +0 -0
  163. {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spawnpoint-studio
3
- Version: 0.2.0
3
+ Version: 0.3.1
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,15 +33,22 @@ Dynamic: license-file
33
33
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
34
34
 
35
35
  <p align="center">
36
- <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/pixel-hopper.gif" width="48%" alt="Pixel Hopper — generated platformer">
37
- <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/pixel-arena.gif" width="48%" alt="Pixel Arena — generated top-down shooter">
36
+ <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/knight-quest.gif" width="48%" alt="Knight's Quest: a cartoon-style sword platformer">
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
+ <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
+ <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">
40
+ <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/night-survivor.gif" width="48%" alt="Night Survivor: auto-fire, gems, level-ups">
41
+ <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/pixel-hopper.gif" width="48%" alt="Pixel Hopper: a Mario-style platformer">
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">
38
43
  </p>
39
- <p align="center"><sub>Both games are built from <code>examples/pixel_hopper.json</code> and
40
- <code>examples/pixel_arena.json</code>: generated sprites, collision traced from pixels, juice on by
41
- default. Recorded with <code>spawnpoint gif</code> (autopilot).</sub></p>
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 cartoon or pixel art, collision traced from the sprites, juice on by default.
46
+ Recorded with <code>spawnpoint gif</code> (autopilot).</sub></p>
42
47
 
43
- <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 ninja
44
- rescue, a dinosaur-egg bubble shooter, a survivor with level-ups and an arena shooter. No install needed.</p>
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 cartoon
49
+ knight with a sword, tower defense, a ninja rescue, a dinosaur-egg bubble shooter, a survivor with level-ups, a farming
50
+ game, an arena shooter and a side-scrolling space shooter. No install needed, and
51
+ they work on phones too (touch controls).</p>
45
52
 
46
53
  Spawn Point is an open-source, autonomous game studio that runs on your own machine. Instead of asking an LLM to type
47
54
  hundreds of lines of GDScript (slow, expensive, and easy to break with Godot 3/4 mix-ups), the LLM
@@ -149,10 +156,7 @@ Only what you asked for changes, and the level layout is kept. Prompts can be in
149
156
  **The model.** [SpawnPoint-Coder-7B](https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-AWQ) is a 7B model
150
157
  fine-tuned to generate Godot 4 games through this pipeline: it writes the game design, tweaks and entity
151
158
  edits as strict JSON, and the compiler turns that into Godot scenes and GDScript. It runs on a single 12 GB
152
- GPU (AWQ 4-bit). v2 on 30 human-written game ideas (English and Vietnamese): 87% fully right (v1: 73%),
153
- 97% of checks passed; on held-out tasks: briefs 88% exact (v1: 36%), Vietnamese → English 97% (v1: 52%),
154
- tweaks 98%, entity edits 91%. It is tuned for 2D platformers and 2D top-down games only; other genres
155
- are no better than a general 7B coder. `spawnpoint llm up` downloads and serves it: the AWQ build on vLLM when
159
+ GPU (AWQ 4-bit). v5 added **side-scrolling shooters** (flight patterns, power-ups, a boss), **Vietnamese prompts read directly** and a small per-genre format instead of one big form; v5.1 fixes prompts that name the hero only indirectly ("a tank shooting monsters"). The downloadable AWQ build gets 88% of a sealed set of 16 human-written prompts for shooters and the v4 fixes fully right, and 80% of 30 human-written game ideas in English and Vietnamese (v4: 87% on those 30; the model card lists every number and where v5.1 is behind). The GGUF Q4_K_M build scores 83% on those 30. Older models still work with `SPAWNPOINT_BRIEF_FORMAT=v4`. It is tuned for the 2D game kinds Spawn Point builds; other genres are no better than a general 7B coder. `spawnpoint llm up` downloads and serves it: the AWQ build on vLLM when
156
160
  Docker and an NVIDIA GPU are present, otherwise the [GGUF build](https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-GGUF)
157
161
  (Q4_K_M, ~4.7 GB) in Ollama. Force one with `--backend vllm|ollama`.
158
162
 
@@ -165,10 +169,29 @@ use `--no-web` to skip and play natively with `spawnpoint run`.
165
169
 
166
170
  ## Sprites: generated or your own
167
171
 
168
- Layer 2 replaces placeholder shapes with **procedural pixel-art sprites** — no assets, no GPU. The
172
+ Layer 2 replaces placeholder shapes with **procedural sprites** — no assets, no GPU — in one of two art styles:
173
+ **pixel art** (the default) or **cartoon** (`"art_style": "cartoon"` in the graph settings, or "cartoon style" in
174
+ the prompt): smooth shaded sprites with a sticker outline, rounded tiles, and a parallax sky with hills, clouds and
175
+ trees. The
169
176
  kind follows the entity (`Goblin` → humanoid, `FireBat` → flyer, `LaserBot` → robot, `Crystal` → gem…)
170
- and details follow the name (`Ninja` → mask, `Fox` → ears and tail, `Spider` → legs). Every sprite has
171
- idle / run / jump / fall / hurt animations driven automatically by an `Animator` component.
177
+ and details follow the name (`Ninja` → mask, `Fox` → ears and tail, `Spider` → legs, a sword-wielder holds a sword).
178
+ Every sprite has idle / run / jump / fall / hurt animations driven automatically by an `Animator` component, plus
179
+ `attack` (played each time its weapon fires) and `die` (a death sequence that plays on after the entity is gone) for
180
+ anything that fights or can be hurt; heroes breathe and blink while they stand.
181
+
182
+ **Pixel-art settings** (`settings` in the graph; the assembler sets the first two from the game's theme):
183
+
184
+ | Setting | Values | What it does |
185
+ |---|---|---|
186
+ | `pixel_palette` | `none`, `pico8`, `sweetie16`, `endesga32` | Limits every sprite, tile and backdrop colour to a classic palette, so the whole game shares one set of colours. |
187
+ | `scenery` | `auto`, `flat`, or a theme (`night`, `forest`, `desert`, `neon`, `candy`, `ocean`, `lava`, `snow`) | A banded sky with sun or moon and stars, parallax clouds, mountains, hills and themed trees, cacti, skylines or kelp (a themed floor for top-down games). `auto` picks one that fits the background colour, `flat` keeps the plain hills. |
188
+ | `pixel_size` | `2` (default), `1` | `2` is chunky (NES-like); `1` draws the same art at twice the resolution (SNES-like) at the same on-screen size. |
189
+
190
+ <p align="center"><img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/pixel-size.png" width="92%" alt="The same game with pixel_size 2 (chunky) and pixel_size 1 (fine)"><br><sub>The same game, same moment: <code>pixel_size: 2</code> (left) and <code>pixel_size: 1</code> (right).</sub></p>
191
+
192
+ Ground tiles are drawn for their neighbours (grass on top, outlined exposed edges, rounded and notched corners,
193
+ varied interiors, capped one-way planks), and pixel-art levels get pixel effects: skidding dust, hit sparks and a
194
+ screen shake in whole pixels.
172
195
 
173
196
  **Collision comes from the pixels.** Hitboxes, hurtboxes, pickups, hazards and static bodies use the
174
197
  sprite's traced outline (`CollisionPolygon2D`); moving bodies keep a smooth capsule/circle fitted to
@@ -332,10 +355,13 @@ headless smoke test (spawn, scripted input, crash detection) and report `E_RUNTI
332
355
  |---|---|
333
356
  | Movement | `PlatformerMovement` (coyote time, jump buffer, variable jump, apex hang, dash, double jump), `TopDownMovement` (accel/friction, dash) |
334
357
  | Controllers | `PlayerInput`, `PatrolAI`, `ChaseAI`, `Aim` |
335
- | Combat | `Health`, `Hitbox`, `Hurtbox`, `Stomp`, `Weapon`, `Projectile` |
358
+ | Combat | `Health`, `Hitbox`, `Hurtbox`, `Stomp`, `Weapon`, `Projectile` (a sword is a `Weapon` whose projectile is a short, piercing slash) |
336
359
  | World | `Collectible`, `TriggerZone`, `Spawner` (ramping waves, spawns around the player), `Gate` |
337
360
  | Progression | `Experience` (XP and levels), `XpOrb` (gems that fly to the player), `LevelUpMenu` (pause and pick one of three upgrades) |
338
361
  | Game modes | `BubbleBoard` (a complete bubble shooter: bank shots, colour pops, falling clusters, a descending ceiling) |
362
+ | Shooters | `FlightPath` (straight, wave, dive and hover patterns across the screen), `ShmupShip` (auto-fire, power-ups that add shots and spread), `HealthBar` (boss bars), and `WaveSpawner` gates (`autostart`, `endless`, random spread) |
363
+ | Tower defense | `PathFollower` (walks the road to the base), `Base` (loses a life per invader that gets in), `WaveSpawner` ("8 x Goblin every 1.2; ..."), `TowerSlot`, `TowerBuilder` (click a pad, pay in gold), `TowerBrain` (targets the invader furthest along), `GroundPatch` (roads and paths) |
364
+ | Farming | `FarmPlot` (soil that grows a crop only while watered), `Farmer` (plant / water / harvest, a basket, crop choice), `Market` (sell the basket), `CropPest` (crows that eat crops) |
339
365
  | Feel | `Juice` — attached to every entity unless `"juice": "none"` |
340
366
 
341
367
  Runtime autoloads: `GameState` (score, lives, variables, win/lose), `World` (spawning, rule
@@ -9,15 +9,22 @@
9
9
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
10
10
 
11
11
  <p align="center">
12
- <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/pixel-hopper.gif" width="48%" alt="Pixel Hopper — generated platformer">
13
- <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/pixel-arena.gif" width="48%" alt="Pixel Arena — generated top-down shooter">
12
+ <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/knight-quest.gif" width="48%" alt="Knight's Quest: a cartoon-style sword platformer">
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
+ <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
+ <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">
16
+ <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/night-survivor.gif" width="48%" alt="Night Survivor: auto-fire, gems, level-ups">
17
+ <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/pixel-hopper.gif" width="48%" alt="Pixel Hopper: a Mario-style platformer">
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">
14
19
  </p>
15
- <p align="center"><sub>Both games are built from <code>examples/pixel_hopper.json</code> and
16
- <code>examples/pixel_arena.json</code>: generated sprites, collision traced from pixels, juice on by
17
- default. Recorded with <code>spawnpoint gif</code> (autopilot).</sub></p>
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 cartoon or pixel art, collision traced from the sprites, juice on by default.
22
+ Recorded with <code>spawnpoint gif</code> (autopilot).</sub></p>
18
23
 
19
- <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 ninja
20
- rescue, a dinosaur-egg bubble shooter, a survivor with level-ups and an arena shooter. No install needed.</p>
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 cartoon
25
+ knight with a sword, tower defense, a ninja rescue, a dinosaur-egg bubble shooter, a survivor with level-ups, a farming
26
+ game, an arena shooter and a side-scrolling space shooter. No install needed, and
27
+ they work on phones too (touch controls).</p>
21
28
 
22
29
  Spawn Point is an open-source, autonomous game studio that runs on your own machine. Instead of asking an LLM to type
23
30
  hundreds of lines of GDScript (slow, expensive, and easy to break with Godot 3/4 mix-ups), the LLM
@@ -125,10 +132,7 @@ Only what you asked for changes, and the level layout is kept. Prompts can be in
125
132
  **The model.** [SpawnPoint-Coder-7B](https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-AWQ) is a 7B model
126
133
  fine-tuned to generate Godot 4 games through this pipeline: it writes the game design, tweaks and entity
127
134
  edits as strict JSON, and the compiler turns that into Godot scenes and GDScript. It runs on a single 12 GB
128
- GPU (AWQ 4-bit). v2 on 30 human-written game ideas (English and Vietnamese): 87% fully right (v1: 73%),
129
- 97% of checks passed; on held-out tasks: briefs 88% exact (v1: 36%), Vietnamese → English 97% (v1: 52%),
130
- tweaks 98%, entity edits 91%. It is tuned for 2D platformers and 2D top-down games only; other genres
131
- are no better than a general 7B coder. `spawnpoint llm up` downloads and serves it: the AWQ build on vLLM when
135
+ GPU (AWQ 4-bit). v5 added **side-scrolling shooters** (flight patterns, power-ups, a boss), **Vietnamese prompts read directly** and a small per-genre format instead of one big form; v5.1 fixes prompts that name the hero only indirectly ("a tank shooting monsters"). The downloadable AWQ build gets 88% of a sealed set of 16 human-written prompts for shooters and the v4 fixes fully right, and 80% of 30 human-written game ideas in English and Vietnamese (v4: 87% on those 30; the model card lists every number and where v5.1 is behind). The GGUF Q4_K_M build scores 83% on those 30. Older models still work with `SPAWNPOINT_BRIEF_FORMAT=v4`. It is tuned for the 2D game kinds Spawn Point builds; other genres are no better than a general 7B coder. `spawnpoint llm up` downloads and serves it: the AWQ build on vLLM when
132
136
  Docker and an NVIDIA GPU are present, otherwise the [GGUF build](https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-GGUF)
133
137
  (Q4_K_M, ~4.7 GB) in Ollama. Force one with `--backend vllm|ollama`.
134
138
 
@@ -141,10 +145,29 @@ use `--no-web` to skip and play natively with `spawnpoint run`.
141
145
 
142
146
  ## Sprites: generated or your own
143
147
 
144
- Layer 2 replaces placeholder shapes with **procedural pixel-art sprites** — no assets, no GPU. The
148
+ Layer 2 replaces placeholder shapes with **procedural sprites** — no assets, no GPU — in one of two art styles:
149
+ **pixel art** (the default) or **cartoon** (`"art_style": "cartoon"` in the graph settings, or "cartoon style" in
150
+ the prompt): smooth shaded sprites with a sticker outline, rounded tiles, and a parallax sky with hills, clouds and
151
+ trees. The
145
152
  kind follows the entity (`Goblin` → humanoid, `FireBat` → flyer, `LaserBot` → robot, `Crystal` → gem…)
146
- and details follow the name (`Ninja` → mask, `Fox` → ears and tail, `Spider` → legs). Every sprite has
147
- idle / run / jump / fall / hurt animations driven automatically by an `Animator` component.
153
+ and details follow the name (`Ninja` → mask, `Fox` → ears and tail, `Spider` → legs, a sword-wielder holds a sword).
154
+ Every sprite has idle / run / jump / fall / hurt animations driven automatically by an `Animator` component, plus
155
+ `attack` (played each time its weapon fires) and `die` (a death sequence that plays on after the entity is gone) for
156
+ anything that fights or can be hurt; heroes breathe and blink while they stand.
157
+
158
+ **Pixel-art settings** (`settings` in the graph; the assembler sets the first two from the game's theme):
159
+
160
+ | Setting | Values | What it does |
161
+ |---|---|---|
162
+ | `pixel_palette` | `none`, `pico8`, `sweetie16`, `endesga32` | Limits every sprite, tile and backdrop colour to a classic palette, so the whole game shares one set of colours. |
163
+ | `scenery` | `auto`, `flat`, or a theme (`night`, `forest`, `desert`, `neon`, `candy`, `ocean`, `lava`, `snow`) | A banded sky with sun or moon and stars, parallax clouds, mountains, hills and themed trees, cacti, skylines or kelp (a themed floor for top-down games). `auto` picks one that fits the background colour, `flat` keeps the plain hills. |
164
+ | `pixel_size` | `2` (default), `1` | `2` is chunky (NES-like); `1` draws the same art at twice the resolution (SNES-like) at the same on-screen size. |
165
+
166
+ <p align="center"><img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/pixel-size.png" width="92%" alt="The same game with pixel_size 2 (chunky) and pixel_size 1 (fine)"><br><sub>The same game, same moment: <code>pixel_size: 2</code> (left) and <code>pixel_size: 1</code> (right).</sub></p>
167
+
168
+ Ground tiles are drawn for their neighbours (grass on top, outlined exposed edges, rounded and notched corners,
169
+ varied interiors, capped one-way planks), and pixel-art levels get pixel effects: skidding dust, hit sparks and a
170
+ screen shake in whole pixels.
148
171
 
149
172
  **Collision comes from the pixels.** Hitboxes, hurtboxes, pickups, hazards and static bodies use the
150
173
  sprite's traced outline (`CollisionPolygon2D`); moving bodies keep a smooth capsule/circle fitted to
@@ -308,10 +331,13 @@ headless smoke test (spawn, scripted input, crash detection) and report `E_RUNTI
308
331
  |---|---|
309
332
  | Movement | `PlatformerMovement` (coyote time, jump buffer, variable jump, apex hang, dash, double jump), `TopDownMovement` (accel/friction, dash) |
310
333
  | Controllers | `PlayerInput`, `PatrolAI`, `ChaseAI`, `Aim` |
311
- | Combat | `Health`, `Hitbox`, `Hurtbox`, `Stomp`, `Weapon`, `Projectile` |
334
+ | Combat | `Health`, `Hitbox`, `Hurtbox`, `Stomp`, `Weapon`, `Projectile` (a sword is a `Weapon` whose projectile is a short, piercing slash) |
312
335
  | World | `Collectible`, `TriggerZone`, `Spawner` (ramping waves, spawns around the player), `Gate` |
313
336
  | Progression | `Experience` (XP and levels), `XpOrb` (gems that fly to the player), `LevelUpMenu` (pause and pick one of three upgrades) |
314
337
  | Game modes | `BubbleBoard` (a complete bubble shooter: bank shots, colour pops, falling clusters, a descending ceiling) |
338
+ | Shooters | `FlightPath` (straight, wave, dive and hover patterns across the screen), `ShmupShip` (auto-fire, power-ups that add shots and spread), `HealthBar` (boss bars), and `WaveSpawner` gates (`autostart`, `endless`, random spread) |
339
+ | Tower defense | `PathFollower` (walks the road to the base), `Base` (loses a life per invader that gets in), `WaveSpawner` ("8 x Goblin every 1.2; ..."), `TowerSlot`, `TowerBuilder` (click a pad, pay in gold), `TowerBrain` (targets the invader furthest along), `GroundPatch` (roads and paths) |
340
+ | Farming | `FarmPlot` (soil that grows a crop only while watered), `Farmer` (plant / water / harvest, a basket, crop choice), `Market` (sell the basket), `CropPest` (crows that eat crops) |
315
341
  | Feel | `Juice` — attached to every entity unless `"juice": "none"` |
316
342
 
317
343
  Runtime autoloads: `GameState` (score, lives, variables, win/lose), `World` (spawning, rule
@@ -46,6 +46,7 @@ class Analysis:
46
46
  rules_source: str | None = None
47
47
  sprites: dict[str, SpriteSet] = field(default_factory=dict) # entity -> resolved sprite sheet
48
48
  tiles_image: Any = None # pixel-art tile atlas (PIL image), written to res://sprites/tiles.png
49
+ scenery_images: dict[str, Any] = field(default_factory=dict) # name -> PIL image, written to res://sprites/<name>.png
49
50
 
50
51
  @property
51
52
  def ok(self) -> bool:
@@ -69,6 +70,7 @@ class _Analyzer:
69
70
  self.genre = GENRE_DEFAULTS[graph.genre]
70
71
  self.sprites: dict[str, SpriteSet] = {}
71
72
  self.tiles_image = None
73
+ self.scenery_images: dict[str, Any] = {}
72
74
 
73
75
  # -- entry -----------------------------------------------------------------------------
74
76
  def run(self) -> Analysis:
@@ -99,7 +101,7 @@ class _Analyzer:
99
101
  if not self.d.ok or data is None:
100
102
  return Analysis(g, self.d)
101
103
  plan = self._plan(data, teams, params_by_entity, players[0])
102
- return Analysis(g, self.d, plan, rules.source, self.sprites, self.tiles_image)
104
+ return Analysis(g, self.d, plan, rules.source, self.sprites, self.tiles_image, self.scenery_images)
103
105
 
104
106
  # -- sprites ---------------------------------------------------------------------------
105
107
  def _resolve_sprites(self, teams: dict[str, str]) -> None:
@@ -110,7 +112,10 @@ class _Analyzer:
110
112
  try:
111
113
  path = join_path("entities", name, "sprite")
112
114
  sprite = sprite_resolve.resolve(name, ent, self.base_dir, color,
113
- warn=lambda m, p=path: self.d.warning("W_AI_SPRITE", p, m))
115
+ warn=lambda m, p=path: self.d.warning("W_AI_SPRITE", p, m),
116
+ style=self.g.settings.art_style,
117
+ palette=self.g.settings.pixel_palette,
118
+ fine=self.g.settings.pixel_size == 1)
114
119
  except SpriteError as e:
115
120
  self.d.error("E_SPRITE", join_path("entities", name, "sprite"), str(e),
116
121
  "Generator kinds: " + ", ".join(sorted(sprite_resolve.KINDS)) if "kind" in str(e) else None)
@@ -120,6 +125,37 @@ class _Analyzer:
120
125
  # No explicit shape: the body collision comes from the drawing.
121
126
  ent.shape = sprite_resolve.derived_shape(ent, sprite, self.g.genre)
122
127
 
128
+ def _scenery(self, background: str) -> dict | None:
129
+ """Pixel-art backdrop images (written next to the sprites) and how the level should lay them out."""
130
+ s = self.g.settings
131
+ from .sprites import scenery as sc
132
+
133
+ if s.scenery == "space": # scrolling stars for shooters, in any art style
134
+ sky, stars = sc.space(background, s.pixel_palette if s.art_style == "pixel" else None, s.seed)
135
+ self.scenery_images["scenery_sky"] = sc.to_image(sky)
136
+ layers = []
137
+ for name, image, speed in stars:
138
+ self.scenery_images[f"scenery_stars_{name}"] = sc.to_image(image)
139
+ layers.append({"name": name, "texture": f"res://sprites/scenery_stars_{name}.png", "speed": speed,
140
+ "height": int(image.shape[0])})
141
+ return {"sky": "res://sprites/scenery_sky.png", "stars": layers, "pixel": 2, "width": sc.WIDTH}
142
+ if s.art_style != "pixel" or s.scenery == "flat" or s.tile_style != "pixel":
143
+ return None
144
+
145
+ theme = s.scenery if s.scenery in sc.THEMES else "auto"
146
+ if self.g.genre == "topdown":
147
+ self.scenery_images["scenery_floor"] = sc.to_image(sc.floor_tile(theme, background, s.pixel_palette, s.seed))
148
+ return {"floor": "res://sprites/scenery_floor.png", "pixel": 2}
149
+ sky, layers = sc.build(theme, background, s.pixel_palette, s.seed)
150
+ self.scenery_images["scenery_sky"] = sc.to_image(sky)
151
+ out = []
152
+ for layer in layers:
153
+ name = f"scenery_{layer.name}"
154
+ self.scenery_images[name] = sc.to_image(layer.image)
155
+ out.append({"name": layer.name, "texture": f"res://sprites/{name}.png", "scroll": layer.scroll,
156
+ "anchor": layer.anchor, "lift": layer.lift, "height": int(layer.image.shape[0])})
157
+ return {"sky": "res://sprites/scenery_sky.png", "layers": out, "pixel": 2, "width": sc.WIDTH}
158
+
123
159
  # -- entities --------------------------------------------------------------------------
124
160
  def _team(self, e: Entity) -> str:
125
161
  if e.team:
@@ -331,11 +367,26 @@ class _Analyzer:
331
367
  "top": (solid and solid.top) or self.genre["solid_top"]},
332
368
  "oneway": {"color": (oneway and oneway.color) or self.genre["oneway"]},
333
369
  }
370
+ variant_of: dict[tuple[int, int], int] = {}
334
371
  if s.tile_style == "pixel":
335
- from .sprites.tiles import tileset
336
- self.tiles_image = tileset(g.genre, tiles_style["solid"]["color"], tiles_style["solid"]["top"],
337
- tiles_style["oneway"]["color"], ts, s.seed)
372
+ from .sprites.tiles import tileset_cartoon
373
+ if s.art_style == "pixel":
374
+ from .sprites.tiles import tileset_auto
375
+ # Pixel ground is drawn for each cell's real neighbours (grass on top, outlines, rounded corners).
376
+ self.tiles_image, kinds, variant_of = tileset_auto(
377
+ g.genre, tiles_style["solid"]["color"], tiles_style["solid"]["top"], tiles_style["oneway"]["color"],
378
+ ts, s.seed, s.pixel_palette, data.tiles, data.width, data.height, s.pixel_size)
379
+ tiles_style["variants"] = kinds
380
+ else:
381
+ self.tiles_image = tileset_cartoon(g.genre, tiles_style["solid"]["color"], tiles_style["solid"]["top"],
382
+ tiles_style["oneway"]["color"], ts, s.seed)
338
383
  tiles_style["texture"] = "res://sprites/tiles.png"
384
+ tiles_style["smooth"] = s.art_style == "cartoon"
385
+ if s.art_style == "cartoon":
386
+ from .sprites.tiles import CARTOON_GAP
387
+ tiles_style["separation"] = CARTOON_GAP
388
+ background = s.background or self.genre["background"]
389
+ scenery_plan = self._scenery(background)
339
390
  return {
340
391
  "format": PLAN_FORMAT,
341
392
  "name": g.name,
@@ -344,11 +395,16 @@ class _Analyzer:
344
395
  "lives": s.lives,
345
396
  "viewport": list(s.viewport),
346
397
  "zoom": s.zoom,
347
- "background": s.background or self.genre["background"],
398
+ "art_style": s.art_style,
399
+ "background": background,
400
+ **({"scenery": scenery_plan} if scenery_plan else {}),
348
401
  "gravity": s.gravity if s.gravity is not None else self.genre["gravity"],
349
402
  "tiles": tiles_style,
350
403
  "map": {"width": data.width, "height": data.height,
351
- "cells": [[x, y, k] for (x, y), k in sorted(data.tiles.items(), key=lambda kv: (kv[0][1], kv[0][0]))]},
404
+ "cells": [[x, y, k] for (x, y), k in sorted(data.tiles.items(), key=lambda kv: (kv[0][1], kv[0][0]))],
405
+ # parallel to cells: the atlas variant drawn for each one (neighbour-aware pixel tiles)
406
+ **({"cell_variants": [variant_of[c] for c, _ in sorted(data.tiles.items(), key=lambda kv: (kv[0][1], kv[0][0]))]}
407
+ if variant_of else {})},
352
408
  "bounds": [0, 0, data.width * ts, data.height * ts],
353
409
  "walls": self.genre["walls"],
354
410
  "entities": entities,
@@ -65,7 +65,8 @@ def build(data: Any, out_dir: Path, *, manifest: Manifest | None = None, smoke:
65
65
 
66
66
  t = time.perf_counter()
67
67
  feature = _godot_feature()
68
- project.write(out_dir, data, plan, analysis.rules_source, feature, analysis.sprites, analysis.tiles_image)
68
+ project.write(out_dir, data, plan, analysis.rules_source, feature, analysis.sprites, analysis.tiles_image,
69
+ analysis.scenery_images)
69
70
  sfx.write_all(out_dir / "sfx", seed=analysis.graph.settings.seed)
70
71
  timings["write"] = time.perf_counter() - t
71
72
 
@@ -93,6 +93,7 @@ def main(argv: list[str] | None = None) -> int:
93
93
  gf.add_argument("--seconds", type=float, default=8.0)
94
94
  gf.add_argument("--fps", type=int, default=20)
95
95
  gf.add_argument("--width", type=int, default=640)
96
+ gf.add_argument("--skip", type=float, default=0.0, help="seconds of play before the clip starts")
96
97
 
97
98
  sp = sub.add_parser("sprites", help="import, generate or preview sprites")
98
99
  sps = sp.add_subparsers(dest="sprites_cmd", required=True)
@@ -245,7 +246,7 @@ def _gif(args) -> int:
245
246
  from .gif import record_gif
246
247
 
247
248
  out = args.out or args.project / "demo.gif"
248
- path = record_gif(args.project.resolve(), out.resolve(), args.seconds, args.fps, args.width)
249
+ path = record_gif(args.project.resolve(), out.resolve(), args.seconds, args.fps, args.width, args.skip)
249
250
  print(f"Wrote {out} ({path.stat().st_size / 1e6:.1f} MB)")
250
251
  return 0
251
252
 
@@ -28,26 +28,50 @@ MODEL = "https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-AWQ"
28
28
  SHOWCASE = [
29
29
  ("pixel_hopper.json", "platformer",
30
30
  "A Mario-style romp: stomp slimes, double-jump, dash, and collect every coin to open the door.",
31
- "Move: arrows / WASD | Jump: Space (twice to double-jump) | Dash: Shift | Restart: R"),
31
+ "Move: arrows / WASD | Jump: Space (twice to double-jump) | Dash: Shift | Restart: R",
32
+ {"pad": "lr", "buttons": [["JUMP", "Space"], ["DASH", "ShiftLeft"]]}),
32
33
  ("ninja_rescue.json", "platformer",
33
34
  "Mom has been caged! Throw shurikens, dodge the masked throwers, grab the key up high and set her free.",
34
- "Move: arrows / WASD | Jump: Space | Shuriken: X / J | Dash: Shift | Restart: R"),
35
+ "Move: arrows / WASD | Jump: Space | Shuriken: X / J | Dash: Shift | Restart: R",
36
+ {"pad": "lr", "buttons": [["JUMP", "Space"], ["STAR", "KeyX"], ["DASH", "ShiftLeft"]]}),
37
+ ("knight_quest.json", "sword platformer",
38
+ "A cartoon knight's run: slash slimes, bats and goblins with your sword, double-jump the spikes and reach the gate.",
39
+ "Move: arrows / WASD | Jump: Space (twice to double-jump) | Sword: X / J | Restart: R",
40
+ {"pad": "lr", "buttons": [["JUMP", "Space"], ["SWORD", "KeyX"]]}),
41
+ ("castle_defense.json", "tower defense",
42
+ "Goblins, orcs and wolves march on the castle: build archer and cannon towers beside the road and hold all seven waves.",
43
+ "Build: click a pad | Tower: 1 archer / 2 cannon | Restart: R",
44
+ {"pad": None, "buttons": [["2", "Digit2"], ["1", "Digit1"]], "tap": "Tap a pad to build; 1 / 2 picks the tower"}),
45
+ ("star_run.json", "side-scrolling shooter",
46
+ "Fly the Falcon through drones, saucers and hunters, grab power-ups for more guns, and bring down the Warship.",
47
+ "Move: arrows / WASD | Fire: automatic | Restart: R",
48
+ {"pad": "dpad", "buttons": [], "tap": "Move with the stick - the ship fires on its own"}),
35
49
  ("dino_eggs.json", "bubble shooter",
36
50
  "The classic egg shooter: bank shots off the walls, pop three dinosaur eggs of a colour, drop whole clusters.",
37
- "Aim: mouse or Left / Right | Shoot: click / Space / X | Restart: R"),
51
+ "Aim: mouse or Left / Right | Shoot: click / Space / X | Restart: R",
52
+ {"pad": None, "buttons": [], "tap": "Tap where you want the egg to go"}),
38
53
  ("night_survivor.json", "survivor",
39
54
  "Survive three minutes of the night: auto-fire, collect gems, level up and pick a new power each time.",
40
- "Move: arrows / WASD | Dash: Shift | Pick an upgrade: 1 / 2 / 3 or click | Restart: R"),
55
+ "Move: arrows / WASD | Dash: Shift | Pick an upgrade: 1 / 2 / 3 or click | Restart: R",
56
+ {"pad": "dpad", "buttons": [["DASH", "ShiftLeft"]], "tap": "Move with the stick - you shoot on your own"}),
57
+ ("sunny_farm.json", "farming",
58
+ "Plant, water and harvest carrots, tomatoes and pumpkins, sell them at the market, and shoo the crows before sunset.",
59
+ "Move: arrows / WASD | Plant, water, harvest: Space / E | Crop: 1 / 2 / 3 | Shoo crows: X | Restart: R",
60
+ {"pad": "dpad", "buttons": [["ACT", "Space"], ["SHOO", "KeyX"], ["3", "Digit3"], ["2", "Digit2"], ["1", "Digit1"]],
61
+ "tap": "Stand next to a plot and press ACT"}),
41
62
  ("pixel_arena.json", "arena shooter",
42
63
  "A top-down arena: blast through robots and turrets, then reach the portal.",
43
- "Move: WASD | Aim: mouse | Shoot: click / X | Dash: Shift | Restart: R"),
64
+ "Move: WASD | Aim: mouse | Shoot: click / X | Dash: Shift | Restart: R",
65
+ {"pad": "dpad", "buttons": [["DASH", "ShiftLeft"]], "tap": "Move with the stick, tap to shoot"}),
44
66
  ]
67
+ TOUCH_JS = Path(__file__).with_name("demo_touch.js")
45
68
 
46
69
 
47
- def game_overlay(controls: str) -> str:
70
+ def game_overlay(controls: str, touch: dict | None = None) -> str:
48
71
  """Injected into every game page: a way back to the gallery and the controls (bottom-left, clear of
49
- the HUD). Hidden for screenshots (?shot)."""
72
+ the HUD), plus the touch controls on phones (../touch.js). Hidden for screenshots (?shot)."""
50
73
  return (
74
+ "<script>window.SP_TOUCH=" + json.dumps(touch or {}) + ";</script><script src=\"../touch.js\"></script>"
51
75
  "<style>#sp-bar{position:fixed;bottom:30px;left:8px;z-index:10;display:flex;gap:10px;align-items:center;"
52
76
  "font:12px/1.4 ui-monospace,Menlo,monospace;color:#fff;background:rgba(0,0,0,.55);padding:5px 10px;"
53
77
  "border-radius:6px;pointer-events:none}#sp-bar a{color:#ffec27;text-decoration:none;pointer-events:auto}"
@@ -67,17 +91,19 @@ class Game:
67
91
  tag: str = ""
68
92
  blurb: str = ""
69
93
  controls: str = ""
94
+ touch: dict = field(default_factory=dict)
70
95
  errors: list[str] = field(default_factory=list)
71
96
 
72
97
 
73
98
  def discover(examples: Path | None = None) -> list[Game]:
74
99
  examples = examples or manifest_mod.EXAMPLES_DIR
75
100
  games: list[Game] = []
76
- for name, tag, blurb, controls in SHOWCASE:
101
+ for name, tag, blurb, controls, touch in SHOWCASE:
77
102
  path = examples / name
78
103
  if path.exists():
79
104
  data = load_graph(path)
80
- games.append(Game(path.stem.replace("_", "-"), path, data.get("name", path.stem), tag, blurb, controls))
105
+ games.append(Game(path.stem.replace("_", "-"), path, data.get("name", path.stem), tag, blurb, controls,
106
+ touch))
81
107
  return games
82
108
 
83
109
 
@@ -93,13 +119,14 @@ def build_site(out: Path, games: list[Game] | None = None, screenshots: bool = T
93
119
  g.errors = [str(d) for d in result.diagnostics.errors] or ["build failed"]
94
120
  log(f" {g.slug}: build failed: {g.errors[0]}")
95
121
  continue
96
- seconds = web.export(project, head_include=game_overlay(g.controls))
122
+ seconds = web.export(project, head_include=game_overlay(g.controls, g.touch))
97
123
  target = out / g.slug
98
124
  shutil.rmtree(target, ignore_errors=True)
99
125
  shutil.copytree(project / web.WEB_DIR, target,
100
126
  ignore=shutil.ignore_patterns(".gdignore", web.VERSION_FILE))
101
127
  share_engine(out, target)
102
128
  log(f" {g.slug}: built (web export {seconds:.1f}s)")
129
+ shutil.copy2(TOUCH_JS, out / "touch.js") # every game page loads it (before the browser check)
103
130
  built = [g for g in games if (out / g.slug / "index.html").exists()]
104
131
  if screenshots:
105
132
  if webcheck.find_chrome() is None:
@@ -178,7 +205,7 @@ def index_html(games: list[Game]) -> str:
178
205
  <meta charset="utf-8">
179
206
  <meta name="viewport" content="width=device-width, initial-scale=1">
180
207
  <title>Spawn Point demos</title>
181
- <meta name="description" content="Five Godot 4 games built with Spawn Point: platformers, a bubble shooter, a survivor. Play in your browser.">
208
+ <meta name="description" content="Nine Godot 4 games built with Spawn Point: platformers, a sword fighter, tower defense, a space shooter, a bubble shooter, a survivor, a farm. Play in your browser.">
182
209
  <meta property="og:title" content="Spawn Point demos: play Godot games in your browser">
183
210
  <meta property="og:description" content="A Mario-style platformer, a ninja rescue, a dinosaur-egg shooter, a survivor and an arena, all built from Spawn Point components.">
184
211
  <style>
@@ -217,8 +244,8 @@ footer {{ color:var(--muted); font-size:13px; padding-bottom:40px }}
217
244
  <body>
218
245
  <header>
219
246
  <h1>Spawn<span>Point</span> demos</h1>
220
- <p class="lede">Five games, five ways to play, all built by Spawn Point from small JSON game graphs and a
221
- library of tested Godot 4 components: game feel, pixel art and collisions come for free. Click a card to play in
247
+ <p class="lede">Nine games, nine ways to play, all built by Spawn Point from small JSON game graphs and a
248
+ library of tested Godot 4 components: game feel, cartoon or pixel art and collisions come for free. Click a card to play in
222
249
  your browser, no install needed.</p>
223
250
  <div class="links">
224
251
  <a href="{REPO}">⭐ GitHub repo</a>
@@ -0,0 +1,118 @@
1
+ // Touch controls for the Spawn Point demo games (phones and tablets).
2
+ // Each game page sets window.SP_TOUCH = {pad: "lr" | "dpad" | null, buttons: [[label, code], ...], tap: "hint"}.
3
+ // Buttons send the same keyboard events a keyboard would, straight to Godot's canvas, so the games need no
4
+ // changes; taps on the canvas itself reach Godot as mouse clicks (aiming, upgrade cards).
5
+ (function () {
6
+ var cfg = window.SP_TOUCH || {};
7
+ var coarse = window.matchMedia && matchMedia("(pointer: coarse)").matches;
8
+ var forced = location.search.indexOf("touch") >= 0; // ?touch shows the controls on a desktop (testing)
9
+ if (!(coarse || forced || "ontouchstart" in window) || location.search.indexOf("shot") >= 0) return;
10
+
11
+ var KEYS = {Space: " ", ShiftLeft: "Shift", KeyX: "x", KeyR: "r", KeyE: "e", Digit1: "1", Digit2: "2", Digit3: "3",
12
+ ArrowLeft: "ArrowLeft",
13
+ ArrowRight: "ArrowRight", ArrowUp: "ArrowUp", ArrowDown: "ArrowDown"};
14
+ var held = {};
15
+ function send(code, on) {
16
+ if (!!held[code] === on) return;
17
+ held[code] = on;
18
+ var canvas = document.getElementById("canvas");
19
+ if (!canvas) return;
20
+ canvas.dispatchEvent(new KeyboardEvent(on ? "keydown" : "keyup",
21
+ {code: code, key: KEYS[code] || code, bubbles: true, cancelable: true}));
22
+ }
23
+
24
+ var css = document.createElement("style");
25
+ css.textContent =
26
+ ".sp-touch{position:fixed;z-index:20;display:flex;gap:12px;user-select:none;-webkit-user-select:none;touch-action:none}" +
27
+ ".sp-btn{width:64px;height:64px;border-radius:50%;background:rgba(255,255,255,.16);border:2px solid rgba(255,255,255,.45);" +
28
+ "color:#fff;font:700 14px/60px ui-monospace,Menlo,monospace;text-align:center;touch-action:none}" +
29
+ ".sp-btn.big{width:84px;height:84px;line-height:80px;font-size:16px}.sp-btn.on{background:rgba(255,236,39,.45)}" +
30
+ ".sp-pad{width:136px;height:136px;border-radius:50%;background:rgba(255,255,255,.12);border:2px solid rgba(255,255,255,.4);" +
31
+ "position:relative;touch-action:none}.sp-knob{position:absolute;left:43px;top:43px;width:46px;height:46px;border-radius:50%;" +
32
+ "background:rgba(255,255,255,.5)}#sp-rotate{position:fixed;inset:0;z-index:30;display:none;align-items:center;" +
33
+ "justify-content:center;background:#14121c;color:#fff;font:600 18px system-ui;text-align:center;padding:24px}" +
34
+ "@media (orientation:portrait){#sp-rotate{display:flex}}#sp-hint{position:fixed;left:50%;top:40%;z-index:20;" +
35
+ "transform:translateX(-50%);color:#fff;background:rgba(0,0,0,.55);padding:8px 14px;border-radius:8px;" +
36
+ "font:600 14px system-ui;pointer-events:none;transition:opacity 1s}#sp-bar span{display:none}"; // keyboard help hides
37
+ document.head.appendChild(css);
38
+
39
+ function button(label, code, big) {
40
+ var b = document.createElement("div");
41
+ b.className = "sp-btn" + (big ? " big" : "");
42
+ b.textContent = label;
43
+ b.addEventListener("pointerdown", function (e) {
44
+ e.preventDefault();
45
+ b.setPointerCapture(e.pointerId);
46
+ b.classList.add("on");
47
+ send(code, true);
48
+ });
49
+ ["pointerup", "pointercancel", "lostpointercapture"].forEach(function (t) {
50
+ b.addEventListener(t, function () { b.classList.remove("on"); send(code, false); });
51
+ });
52
+ return b;
53
+ }
54
+
55
+ function dpad() {
56
+ var pad = document.createElement("div"), knob = document.createElement("div");
57
+ pad.className = "sp-pad";
58
+ knob.className = "sp-knob";
59
+ pad.appendChild(knob);
60
+ function update(e) {
61
+ var r = pad.getBoundingClientRect();
62
+ var dx = (e.clientX - r.left - r.width / 2) / (r.width / 2);
63
+ var dy = (e.clientY - r.top - r.height / 2) / (r.height / 2);
64
+ var len = Math.sqrt(dx * dx + dy * dy) || 1, k = Math.min(1, len) / len;
65
+ knob.style.transform = "translate(" + dx * k * 45 + "px," + dy * k * 45 + "px)";
66
+ send("ArrowLeft", dx < -0.35); send("ArrowRight", dx > 0.35);
67
+ send("ArrowUp", dy < -0.35); send("ArrowDown", dy > 0.35);
68
+ }
69
+ function release() {
70
+ knob.style.transform = "";
71
+ ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].forEach(function (c) { send(c, false); });
72
+ }
73
+ pad.addEventListener("pointerdown", function (e) { e.preventDefault(); pad.setPointerCapture(e.pointerId); update(e); });
74
+ pad.addEventListener("pointermove", function (e) { if (pad.hasPointerCapture(e.pointerId)) update(e); });
75
+ ["pointerup", "pointercancel", "lostpointercapture"].forEach(function (t) { pad.addEventListener(t, release); });
76
+ return pad;
77
+ }
78
+
79
+ addEventListener("DOMContentLoaded", function () {
80
+ var rotate = document.createElement("div");
81
+ rotate.id = "sp-rotate";
82
+ rotate.textContent = "Turn your phone sideways to play ↻";
83
+ document.body.appendChild(rotate);
84
+
85
+ var left = document.createElement("div");
86
+ left.className = "sp-touch";
87
+ left.style.cssText = "left:18px;bottom:64px";
88
+ if (cfg.pad === "lr") {
89
+ left.appendChild(button("◀", "ArrowLeft", true));
90
+ left.appendChild(button("▶", "ArrowRight", true));
91
+ } else if (cfg.pad === "dpad") {
92
+ left.appendChild(dpad());
93
+ }
94
+ document.body.appendChild(left);
95
+
96
+ var right = document.createElement("div");
97
+ right.className = "sp-touch";
98
+ right.style.cssText = "right:18px;bottom:64px;flex-direction:row-reverse;align-items:flex-end";
99
+ (cfg.buttons || []).forEach(function (b, i) { right.appendChild(button(b[0], b[1], i === 0)); });
100
+ document.body.appendChild(right);
101
+
102
+ var restart = document.createElement("div");
103
+ restart.className = "sp-touch";
104
+ restart.style.cssText = "right:14px;top:48px";
105
+ var r = button("↻", "KeyR");
106
+ r.style.cssText = "width:44px;height:44px;line-height:40px;font-size:20px";
107
+ restart.appendChild(r);
108
+ document.body.appendChild(restart);
109
+
110
+ if (cfg.tap) {
111
+ var hint = document.createElement("div");
112
+ hint.id = "sp-hint";
113
+ hint.textContent = cfg.tap;
114
+ document.body.appendChild(hint);
115
+ setTimeout(function () { hint.style.opacity = "0"; }, 3500);
116
+ }
117
+ });
118
+ })();