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.
- {spawnpoint_studio-0.2.0/compiler/spawnpoint_studio.egg-info → spawnpoint_studio-0.3.1}/PKG-INFO +42 -16
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/README.md +41 -15
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/analyze.py +63 -7
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/build.py +2 -1
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/cli.py +2 -1
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/demo.py +40 -13
- spawnpoint_studio-0.3.1/compiler/spawnpoint/demo_touch.js +118 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/gif.py +10 -7
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/graph.py +8 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/llm_server.py +5 -2
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/project.py +3 -1
- spawnpoint_studio-0.3.1/compiler/spawnpoint/sprites/cartoon.py +477 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/sprites/generate.py +195 -14
- spawnpoint_studio-0.3.1/compiler/spawnpoint/sprites/motion.py +72 -0
- spawnpoint_studio-0.3.1/compiler/spawnpoint/sprites/palettes.py +111 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/sprites/resolve.py +17 -4
- spawnpoint_studio-0.3.1/compiler/spawnpoint/sprites/scenery.py +471 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/sprites/spriteset.py +7 -5
- spawnpoint_studio-0.3.1/compiler/spawnpoint/sprites/tiles.py +362 -0
- spawnpoint_studio-0.3.1/compiler/spawnpoint/studio/assemble.py +1127 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/brief.py +123 -26
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/edits.py +28 -0
- spawnpoint_studio-0.3.1/compiler/spawnpoint/studio/genres.py +248 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/levelgen.py +99 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/orchestrator.py +31 -15
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/prompts.py +50 -4
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/webcheck.py +7 -3
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio_cli.py +10 -1
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1/compiler/spawnpoint_studio.egg-info}/PKG-INFO +42 -16
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint_studio.egg-info/SOURCES.txt +25 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/conftest.py +7 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_demo.py +2 -1
- spawnpoint_studio-0.3.1/compiler/tests/test_genres.py +207 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_live_llm.py +5 -0
- spawnpoint_studio-0.3.1/compiler/tests/test_sprites.py +537 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_studio.py +103 -9
- spawnpoint_studio-0.3.1/examples/castle_defense.json +341 -0
- spawnpoint_studio-0.3.1/examples/knight_quest.json +348 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/examples/night_survivor.json +42 -22
- spawnpoint_studio-0.3.1/examples/star_run.json +444 -0
- spawnpoint_studio-0.3.1/examples/sunny_farm.json +223 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/animator_component.gd +18 -2
- spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/base_component.gd +31 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/bubble_board_component.gd +16 -2
- spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/crop_pest_component.gd +77 -0
- spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/farm_plot_component.gd +159 -0
- spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/farmer_component.gd +158 -0
- spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/flight_path_component.gd +101 -0
- spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/ground_patch_component.gd +37 -0
- spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/health_bar_component.gd +41 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/juice_component.gd +36 -2
- spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/market_component.gd +52 -0
- spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/path_follower_component.gd +95 -0
- spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/shmup_ship_component.gd +65 -0
- spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/tower_brain_component.gd +39 -0
- spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/tower_builder_component.gd +105 -0
- spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/tower_slot_component.gd +47 -0
- spawnpoint_studio-0.3.1/godot/addons/spawn_point/components/wave_spawner_component.gd +106 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/manifest.json +785 -16
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/camera_rig.gd +3 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/entity_core.gd +2 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/fx.gd +95 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/hud.gd +4 -1
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/level.gd +3 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/build.gd +256 -11
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/playtest_nav.gd +3 -1
- spawnpoint_studio-0.3.1/godot/addons/spawn_point/tools/record.gd +233 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/pyproject.toml +2 -2
- spawnpoint_studio-0.2.0/compiler/spawnpoint/sprites/tiles.py +0 -102
- spawnpoint_studio-0.2.0/compiler/spawnpoint/studio/assemble.py +0 -570
- spawnpoint_studio-0.2.0/compiler/tests/test_sprites.py +0 -261
- spawnpoint_studio-0.2.0/godot/addons/spawn_point/tools/record.gd +0 -90
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/LICENSE +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/MANIFEST.in +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/__init__.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/cockpit/__init__.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/cockpit/server.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/cockpit/static/app.css +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/cockpit/static/app.js +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/cockpit/static/index.html +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/diagnostics.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/expr.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/godot.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/level.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/manifest.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/playtest/__init__.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/playtest/report.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/playtest/runner.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/playtest/tuner.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/py.typed +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/rules.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/sfx.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/sprites/__init__.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/sprites/ai.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/sprites/contour.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/sprites/importer.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/__init__.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/creative.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/dataset.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/llm.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint/studio/web.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint_studio.egg-info/dependency_links.txt +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint_studio.egg-info/entry_points.txt +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint_studio.egg-info/requires.txt +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/spawnpoint_studio.egg-info/top_level.txt +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_ai_sprites.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_analyze.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_cockpit.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_e2e.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_expr.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_gif.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_godot_library.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_graph.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_level.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_manifest.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_playtest.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_rules.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_sfx.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/compiler/tests/test_web.py +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/examples/coin_hopper.json +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/examples/dino_eggs.json +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/examples/neon_arena.json +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/examples/ninja_rescue.json +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/examples/pixel_arena.json +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/examples/pixel_hopper.json +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/aim_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/chase_ai_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/collectible_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/experience_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/gate_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/health_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/hitbox_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/hurtbox_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/level_up_menu_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/patrol_ai_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/platformer_movement_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/player_input_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/projectile_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/spawner_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/stomp_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/top_down_movement_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/trigger_zone_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/weapon_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/components/xp_orb_component.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/cockpit.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/game_state.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/sfx.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/sp.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/runtime/world.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/shaders/flash.gdshader +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/shaders/floor_grid.gdshader +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/build.tscn +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/capture.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/capture.tscn +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/dump_manifest.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/dump_manifest.tscn +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/playtest.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/playtest.tscn +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/record.tscn +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/smoke.gd +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/godot/addons/spawn_point/tools/smoke.tscn +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/setup.cfg +0 -0
- {spawnpoint_studio-0.2.0 → spawnpoint_studio-0.3.1}/setup.py +0 -0
{spawnpoint_studio-0.2.0/compiler/spawnpoint_studio.egg-info → spawnpoint_studio-0.3.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spawnpoint-studio
|
|
3
|
-
Version: 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)
|
|
34
34
|
|
|
35
35
|
<p align="center">
|
|
36
|
-
<img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/
|
|
37
|
-
<img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/
|
|
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>
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
44
|
-
rescue, a dinosaur-egg bubble shooter, a survivor with level-ups
|
|
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).
|
|
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
|
|
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
|
|
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)
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/
|
|
13
|
-
<img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/
|
|
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>
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
20
|
-
rescue, a dinosaur-egg bubble shooter, a survivor with level-ups
|
|
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).
|
|
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
|
|
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
|
|
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
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
"
|
|
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="
|
|
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">
|
|
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
|
+
})();
|