summer-engine 3.1.0 → 3.2.0
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +27 -5
- package/.codex-plugin/plugin.json +28 -6
- package/.cursor-plugin/plugin.json +27 -5
- package/.factory-plugin/plugin.json +27 -5
- package/.opencode/plugins/summer.js +1 -1
- package/CHANGELOG.md +102 -0
- package/README.md +18 -3
- package/assets/audit/scene_audit.gd +4136 -0
- package/assets/seeing/seeing_probe.gd +1873 -0
- package/dist/cli/commands/skills.js +20 -2
- package/dist/core/api-client.d.ts +9 -1
- package/dist/core/api-client.js +21 -4
- package/dist/core/capabilities/agent-playbook.js +46 -0
- package/dist/core/capabilities/audit/accept.d.ts +55 -0
- package/dist/core/capabilities/audit/accept.js +147 -0
- package/dist/core/capabilities/audit/args.d.ts +95 -0
- package/dist/core/capabilities/audit/args.js +89 -0
- package/dist/core/capabilities/audit/audit.d.ts +56 -0
- package/dist/core/capabilities/audit/audit.js +287 -0
- package/dist/core/capabilities/audit/frame.d.ts +14 -0
- package/dist/core/capabilities/audit/frame.js +35 -0
- package/dist/core/capabilities/audit/gaps.d.ts +96 -0
- package/dist/core/capabilities/audit/gaps.js +257 -0
- package/dist/core/capabilities/audit/judge.d.ts +162 -0
- package/dist/core/capabilities/audit/judge.js +1134 -0
- package/dist/core/capabilities/audit/math.d.ts +110 -0
- package/dist/core/capabilities/audit/math.js +263 -0
- package/dist/core/capabilities/audit/summary.d.ts +74 -0
- package/dist/core/capabilities/audit/summary.js +212 -0
- package/dist/core/capabilities/board.d.ts +31 -0
- package/dist/core/capabilities/board.js +43 -0
- package/dist/core/capabilities/camera-view.d.ts +4 -2
- package/dist/core/capabilities/camera-view.js +6 -3
- package/dist/core/capabilities/connect-signal.d.ts +85 -0
- package/dist/core/capabilities/connect-signal.js +285 -0
- package/dist/core/capabilities/engine-fallbacks.d.ts +5 -0
- package/dist/core/capabilities/engine-fallbacks.js +5 -0
- package/dist/core/capabilities/engine-ops.d.ts +5 -1
- package/dist/core/capabilities/engine-ops.js +0 -3
- package/dist/core/capabilities/engine-receipt.d.ts +12 -0
- package/dist/core/capabilities/engine-receipt.js +26 -0
- package/dist/core/capabilities/file-read.d.ts +64 -0
- package/dist/core/capabilities/file-read.js +325 -0
- package/dist/core/capabilities/game-task-plan.js +4 -2
- package/dist/core/capabilities/grep.d.ts +60 -0
- package/dist/core/capabilities/grep.js +190 -0
- package/dist/core/capabilities/inspect-node.d.ts +56 -0
- package/dist/core/capabilities/inspect-node.js +225 -0
- package/dist/core/capabilities/inspect-resource.d.ts +61 -0
- package/dist/core/capabilities/inspect-resource.js +376 -0
- package/dist/core/capabilities/math3d.d.ts +28 -0
- package/dist/core/capabilities/math3d.js +88 -0
- package/dist/core/capabilities/motion-generation.d.ts +62 -0
- package/dist/core/capabilities/motion-generation.js +138 -0
- package/dist/core/capabilities/placement-batch.d.ts +102 -0
- package/dist/core/capabilities/placement-batch.js +483 -0
- package/dist/core/capabilities/placement-math.d.ts +70 -0
- package/dist/core/capabilities/placement-math.js +209 -0
- package/dist/core/capabilities/placement-script.d.ts +42 -0
- package/dist/core/capabilities/placement-script.js +1096 -0
- package/dist/core/capabilities/placement.d.ts +485 -0
- package/dist/core/capabilities/placement.js +1731 -0
- package/dist/core/capabilities/project-context.d.ts +33 -2
- package/dist/core/capabilities/project-context.js +87 -4
- package/dist/core/capabilities/replace-node.d.ts +91 -0
- package/dist/core/capabilities/replace-node.js +423 -0
- package/dist/core/capabilities/runtime-control.d.ts +18 -2
- package/dist/core/capabilities/runtime-control.js +67 -2
- package/dist/core/capabilities/scene-batch.d.ts +63 -0
- package/dist/core/capabilities/scene-batch.js +394 -0
- package/dist/core/capabilities/scene-readback.d.ts +36 -0
- package/dist/core/capabilities/scene-readback.js +54 -0
- package/dist/core/capabilities/seeing/args.d.ts +348 -0
- package/dist/core/capabilities/seeing/args.js +178 -0
- package/dist/core/capabilities/seeing/candidates.d.ts +127 -0
- package/dist/core/capabilities/seeing/candidates.js +282 -0
- package/dist/core/capabilities/seeing/math.d.ts +134 -0
- package/dist/core/capabilities/seeing/math.js +269 -0
- package/dist/core/capabilities/seeing/probe.d.ts +62 -0
- package/dist/core/capabilities/seeing/probe.js +188 -0
- package/dist/core/capabilities/seeing/scoring.d.ts +235 -0
- package/dist/core/capabilities/seeing/scoring.js +639 -0
- package/dist/core/capabilities/seeing/seeing.d.ts +233 -0
- package/dist/core/capabilities/seeing/seeing.js +1341 -0
- package/dist/core/capabilities/seeing/shot-store.d.ts +52 -0
- package/dist/core/capabilities/seeing/shot-store.js +204 -0
- package/dist/core/capabilities/surface-snap-mesh.d.ts +88 -0
- package/dist/core/capabilities/surface-snap-mesh.js +425 -0
- package/dist/core/capabilities/surface-snap.d.ts +77 -0
- package/dist/core/capabilities/surface-snap.js +461 -0
- package/dist/core/capabilities/tool-dispatch.js +231 -72
- package/dist/core/capabilities/tscn.d.ts +85 -0
- package/dist/core/capabilities/tscn.js +391 -0
- package/dist/core/capabilities/world-snapshot.d.ts +58 -0
- package/dist/core/capabilities/world-snapshot.js +150 -0
- package/dist/core/capability-skew.d.ts +5 -5
- package/dist/core/capability-skew.js +14 -6
- package/dist/core/headless/worker-engine-client.d.ts +1 -1
- package/dist/core/headless/worker-engine-client.js +2 -2
- package/dist/core/library-read.d.ts +37 -5
- package/dist/core/library-read.js +264 -33
- package/dist/core/library-search.d.ts +2 -2
- package/dist/installer/agent-config.js +5 -3
- package/dist/installer/agent-table.d.ts +16 -0
- package/dist/installer/agent-table.js +52 -17
- package/dist/mcp/server.js +9 -3
- package/dist/mcp/tools/asset-tools.js +21 -3
- package/dist/mcp/tools/audit-tools.d.ts +23 -0
- package/dist/mcp/tools/audit-tools.js +60 -0
- package/dist/mcp/tools/debug-tools.js +3 -1
- package/dist/mcp/tools/file-tools.js +17 -4
- package/dist/mcp/tools/generate-tools.js +74 -66
- package/dist/mcp/tools/library-tools.js +2 -0
- package/dist/mcp/tools/perception-tools.js +6 -19
- package/dist/mcp/tools/placement-tools.d.ts +24 -0
- package/dist/mcp/tools/placement-tools.js +91 -0
- package/dist/mcp/tools/project-tools.js +18 -15
- package/dist/mcp/tools/scene-tools.js +71 -83
- package/dist/mcp/tools/seeing-tools.d.ts +24 -0
- package/dist/mcp/tools/seeing-tools.js +64 -0
- package/dist/mcp/tools/spatial-tools.js +17 -9
- package/dist/mcp/tools/visual-tools.js +59 -7
- package/gemini-extension.json +26 -4
- package/integrations/amp/README.md +1 -1
- package/integrations/crush/README.md +1 -1
- package/integrations/grok-build/README.md +1 -1
- package/integrations/hermes/README.md +1 -1
- package/integrations/kimi-code/README.md +1 -1
- package/integrations/mistral-vibe/README.md +1 -1
- package/integrations/opencode/README.md +1 -1
- package/integrations/rovo-dev/README.md +1 -1
- package/integrations/vscode-copilot/README.md +1 -1
- package/integrations/warp/README.md +1 -1
- package/integrations/windsurf/README.md +1 -1
- package/library/references/mcp-tools-reference/mcp-tools-reference.md +59 -15
- package/library/references/mcp-tools-reference/resource.yaml +1 -1
- package/library/references/summer-folder/resource.yaml +1 -1
- package/library/references/summer-folder/summer-folder.md +1 -1
- package/library/skills/3d-lighting/SKILL.md +1 -1
- package/library/skills/README.md +36 -14
- package/library/skills/adaptive-music/SKILL.md +1 -1
- package/library/skills/agent-playtesting/SKILL.md +1 -1
- package/library/skills/ai-characters-in-games/SKILL.md +220 -0
- package/library/skills/ai-characters-in-games/resource.yaml +30 -0
- package/library/skills/ambient-bed/SKILL.md +1 -1
- package/library/skills/animated-loop/SKILL.md +1 -1
- package/library/skills/animation-tree/SKILL.md +1 -1
- package/library/skills/art-direction/SKILL.md +1 -1
- package/library/skills/asset-strategy/SKILL.md +2 -1
- package/library/skills/asset-strategy/resource.yaml +2 -1
- package/library/skills/audio-direction/SKILL.md +1 -1
- package/library/skills/auto-fire-targeting/SKILL.md +1 -1
- package/library/skills/brainstorm-game/SKILL.md +1 -1
- package/library/skills/brainstorming/SKILL.md +1 -1
- package/library/skills/browse-templates/SKILL.md +3 -3
- package/library/skills/browse-templates/resource.yaml +1 -1
- package/library/skills/camera-rigs/SKILL.md +128 -0
- package/library/skills/camera-rigs/resource.yaml +29 -0
- package/library/skills/celeste-momentum-platforming/SKILL.md +1 -1
- package/library/skills/character-animation-wiring/SKILL.md +1 -1
- package/library/skills/character-model/SKILL.md +1 -1
- package/library/skills/character-movement/SKILL.md +326 -0
- package/library/skills/character-movement/references/default-third-person-controller.gd +201 -0
- package/library/skills/character-movement/references/default-third-person-verification-probe.gd +154 -0
- package/library/skills/character-movement/references/movement-3d.md +136 -0
- package/library/skills/character-movement/references/platformer-feel.md +83 -0
- package/library/skills/character-movement/resource.yaml +40 -0
- package/library/skills/character-portrait/SKILL.md +1 -1
- package/library/skills/cinematic-cutscene/SKILL.md +1 -1
- package/library/skills/combat-basics/SKILL.md +159 -0
- package/library/skills/combat-basics/references/projectiles-and-aoe.md +100 -0
- package/library/skills/combat-basics/resource.yaml +34 -0
- package/library/skills/concept-art/SKILL.md +1 -1
- package/library/skills/create-asset-sheet/SKILL.md +1 -1
- package/library/skills/debug/SKILL.md +1 -1
- package/library/skills/debugging-game-feel/SKILL.md +1 -1
- package/library/skills/design-level/SKILL.md +1 -1
- package/library/skills/design-mechanic/SKILL.md +1 -1
- package/library/skills/design-npc/SKILL.md +1 -1
- package/library/skills/diagnosing-perf-regressions/SKILL.md +1 -1
- package/library/skills/dispatching-parallel-agents/SKILL.md +1 -1
- package/library/skills/driving-the-editor-ui/SKILL.md +1 -1
- package/library/skills/environment-kit/SKILL.md +1 -1
- package/library/skills/export-and-ship/SKILL.md +1 -1
- package/library/skills/fabricating-assets/SKILL.md +1 -1
- package/library/skills/facial-and-lipsync/SKILL.md +1 -1
- package/library/skills/fps-controller/SKILL.md +9 -7
- package/library/skills/fps-controller/resource.yaml +4 -1
- package/library/skills/game-feel/SKILL.md +1 -1
- package/library/skills/gameskill/SKILL.md +1 -1
- package/library/skills/gdscript-patterns/SKILL.md +1 -1
- package/library/skills/generate-motion/SKILL.md +9 -9
- package/library/skills/generate-motion/resource.yaml +2 -1
- package/library/skills/godogen-visual-proof-game-generation/SKILL.md +1 -1
- package/library/skills/headless-scripting/SKILL.md +1 -1
- package/library/skills/in-game-purchases/SKILL.md +254 -0
- package/library/skills/in-game-purchases/resource.yaml +29 -0
- package/library/skills/instantiate-asset-pack/SKILL.md +1 -1
- package/library/skills/investigating-bugs/SKILL.md +1 -1
- package/library/skills/lumera-single-image-scene-reconstruction/SKILL.md +1 -1
- package/library/skills/make-game/SKILL.md +25 -3
- package/library/skills/make-game/references/2d-platformer-polish.md +93 -0
- package/library/skills/make-game/references/2d-platformer.md +117 -0
- package/library/skills/make-game/references/2d-rpg-content.md +234 -0
- package/library/skills/make-game/references/2d-rpg.md +241 -0
- package/library/skills/make-game/references/2d-survivors-progression.md +201 -0
- package/library/skills/make-game/references/2d-survivors.md +237 -0
- package/library/skills/make-game/references/3d-fps-systems.md +216 -0
- package/library/skills/make-game/references/3d-fps.md +177 -0
- package/library/skills/make-game/references/3d-horror-systems.md +186 -0
- package/library/skills/make-game/references/3d-horror.md +201 -0
- package/library/skills/make-game/references/3d-third-person-systems.md +220 -0
- package/library/skills/make-game/references/3d-third-person.md +215 -0
- package/library/skills/make-game/resource.yaml +6 -1
- package/library/skills/multiplayer/SKILL.md +150 -0
- package/library/skills/multiplayer/resource.yaml +33 -0
- package/library/skills/multiplayer-movement/SKILL.md +510 -0
- package/library/skills/multiplayer-movement/files.md +501 -0
- package/library/skills/multiplayer-movement/resource.yaml +30 -0
- package/library/skills/multiplayer-project/SKILL.md +521 -0
- package/library/skills/multiplayer-project/files.md +448 -0
- package/library/skills/multiplayer-project/resource.yaml +36 -0
- package/library/skills/multiplayer-publish/SKILL.md +229 -0
- package/library/skills/multiplayer-publish/export-errors.md +89 -0
- package/library/skills/multiplayer-publish/resource.yaml +28 -0
- package/library/skills/multiplayer-state/SKILL.md +438 -0
- package/library/skills/multiplayer-state/files.md +592 -0
- package/library/skills/multiplayer-state/resource.yaml +35 -0
- package/library/skills/multiplayer-testing/SKILL.md +236 -0
- package/library/skills/multiplayer-testing/resource.yaml +39 -0
- package/library/skills/music-track/SKILL.md +1 -1
- package/library/skills/navigate-summer/SKILL.md +1 -1
- package/library/skills/new-project/SKILL.md +1 -1
- package/library/skills/organic-model/SKILL.md +1 -1
- package/library/skills/pixel-art/SKILL.md +1 -1
- package/library/skills/play/SKILL.md +1 -1
- package/library/skills/playtesting-a-feature/SKILL.md +1 -1
- package/library/skills/procedural-animation/SKILL.md +2 -2
- package/library/skills/procedural-animation/resource.yaml +1 -1
- package/library/skills/prop-model/SKILL.md +1 -1
- package/library/skills/psx-retro-rendering/SKILL.md +1 -1
- package/library/skills/realtime-wet-surfaces/SKILL.md +2 -2
- package/library/skills/remote-deploy/SKILL.md +1 -1
- package/library/skills/retarget/SKILL.md +1 -1
- package/library/skills/running-in-the-cloud/SKILL.md +1 -1
- package/library/skills/save-load/SKILL.md +156 -0
- package/library/skills/save-load/resource.yaml +27 -0
- package/library/skills/scene-composition/SKILL.md +1 -1
- package/library/skills/scene-hierarchy-design/SKILL.md +1 -1
- package/library/skills/scene-scripting/SKILL.md +1 -1
- package/library/skills/scene-to-level/SKILL.md +1 -1
- package/library/skills/skill-create/SKILL.md +1 -1
- package/library/skills/skill-improve/SKILL.md +1 -1
- package/library/skills/skill-test/SKILL.md +1 -1
- package/library/skills/skintokens-auto-rigging/SKILL.md +1 -1
- package/library/skills/skybox-panorama/SKILL.md +1 -1
- package/library/skills/sound-effect/SKILL.md +1 -1
- package/library/skills/spatial-placement/SKILL.md +134 -3
- package/library/skills/spatial-placement/references/kit-placement-tools.md +364 -0
- package/library/skills/spatial-placement/resource.yaml +16 -2
- package/library/skills/sprite-sheet/SKILL.md +1 -1
- package/library/skills/summer-analytics/SKILL.md +147 -0
- package/library/skills/summer-analytics/resource.yaml +28 -0
- package/library/skills/summer-friends/SKILL.md +226 -0
- package/library/skills/summer-friends/resource.yaml +28 -0
- package/library/skills/summer-leaderboards/SKILL.md +231 -0
- package/library/skills/summer-leaderboards/resource.yaml +30 -0
- package/library/skills/summer-match-results/SKILL.md +286 -0
- package/library/skills/summer-match-results/resource.yaml +32 -0
- package/library/skills/summer-matchmaking/SKILL.md +301 -0
- package/library/skills/summer-matchmaking/resource.yaml +32 -0
- package/library/skills/summer-parties/SKILL.md +253 -0
- package/library/skills/summer-parties/resource.yaml +30 -0
- package/library/skills/summer-player-data/SKILL.md +322 -0
- package/library/skills/summer-player-data/resource.yaml +30 -0
- package/library/skills/summer-store/SKILL.md +271 -0
- package/library/skills/summer-store/resource.yaml +29 -0
- package/library/skills/summer-world-chat/SKILL.md +195 -0
- package/library/skills/summer-world-chat/resource.yaml +30 -0
- package/library/skills/summer-world-saves/SKILL.md +284 -0
- package/library/skills/summer-world-saves/resource.yaml +29 -0
- package/library/skills/text-to-motion/SKILL.md +99 -0
- package/library/skills/text-to-motion/prompts-and-controls.md +34 -0
- package/library/skills/text-to-motion/resource.yaml +41 -0
- package/library/skills/text-to-motion/rigging-for-text-motion.md +54 -0
- package/library/skills/tileable-texture/SKILL.md +1 -1
- package/library/skills/trailer-shot/SKILL.md +1 -1
- package/library/skills/tune-performance/SKILL.md +1 -1
- package/library/skills/ui-basics/SKILL.md +10 -1
- package/library/skills/ui-graphics/SKILL.md +1 -1
- package/library/skills/use-widget-asset/SKILL.md +1 -1
- package/library/skills/using-summer/SKILL.md +2 -2
- package/library/skills/using-summer/resource.yaml +2 -2
- package/library/skills/vehicle-model/SKILL.md +1 -1
- package/library/skills/verification-before-completion/SKILL.md +1 -1
- package/library/skills/verifying-scenes/SKILL.md +76 -2
- package/library/skills/verifying-scenes/resource.yaml +13 -2
- package/library/skills/vfx-dissolve/SKILL.md +1 -1
- package/library/skills/vfx-fire/SKILL.md +1 -1
- package/library/skills/vfx-hit-spark/SKILL.md +1 -1
- package/library/skills/vfx-lightning/SKILL.md +1 -1
- package/library/skills/vfx-magic-glow/SKILL.md +1 -1
- package/library/skills/vfx-muzzle-flash/SKILL.md +1 -1
- package/library/skills/vfx-smoke/SKILL.md +1 -1
- package/library/skills/vfx-water-ripple/SKILL.md +1 -1
- package/library/skills/voice-line/SKILL.md +1 -1
- package/library/skills/web-and-touch-input/SKILL.md +38 -0
- package/library/skills/web-and-touch-input/resource.yaml +28 -0
- package/library/skills/web-compatibility-look/SKILL.md +62 -0
- package/library/skills/web-compatibility-look/resource.yaml +30 -0
- package/library/skills/world-building-3d/SKILL.md +8 -1
- package/library/skills/world-building-3d/references/spatial-tools.md +1 -1
- package/library/skills/world-building-3d/resource.yaml +2 -1
- package/library/skills/writing-plans/SKILL.md +1 -1
- package/library/skills/writing-skills/SKILL.md +1 -1
- package/library/templates/3d-lan-multiplayer-starter/resource.yaml +2 -2
- package/library/tools/attach-to-surface/resource.yaml +142 -0
- package/library/tools/batch/resource.yaml +9 -3
- package/library/tools/connect-ports/resource.yaml +120 -0
- package/library/tools/connect-signal/resource.yaml +3 -2
- package/library/tools/debug-views/resource.yaml +94 -0
- package/library/tools/frame-nodes/resource.yaml +104 -0
- package/library/tools/frame-shot/resource.yaml +134 -0
- package/library/tools/generate-motion/resource.yaml +44 -9
- package/library/tools/get-board/resource.yaml +50 -0
- package/library/tools/get-project-context/resource.yaml +13 -2
- package/library/tools/grep/resource.yaml +77 -0
- package/library/tools/inspect-asset/resource.yaml +71 -0
- package/library/tools/inspect-node/resource.yaml +9 -2
- package/library/tools/inspect-resource/resource.yaml +16 -5
- package/library/tools/instantiate-scene/resource.yaml +27 -2
- package/library/tools/measure/resource.yaml +96 -0
- package/library/tools/place-adjacent/resource.yaml +113 -0
- package/library/tools/play/resource.yaml +15 -2
- package/library/tools/raycast/resource.yaml +101 -0
- package/library/tools/read-file/resource.yaml +36 -2
- package/library/tools/read-library/resource.yaml +2 -2
- package/library/tools/repeat-along/resource.yaml +129 -0
- package/library/tools/replace-node/resource.yaml +11 -5
- package/library/tools/scene-audit/resource.yaml +144 -0
- package/library/tools/screenshot/resource.yaml +13 -2
- package/library/tools/search-assets/resource.yaml +18 -0
- package/library/tools/shot-sheet/resource.yaml +110 -0
- package/library/tools/snap-to-surface/resource.yaml +4 -1
- package/library/tools/world-snapshot/resource.yaml +24 -3
- package/library/tools/zoom/resource.yaml +105 -0
- package/package.json +1 -1
- package/registry/generated/aliases.json +17 -3
- package/registry/generated/counts.json +3 -3
- package/registry/generated/gemini-extension.json +26 -4
- package/registry/generated/index.json +2154 -469
- package/registry/generated/marketplace.claude.json +2 -2
- package/registry/generated/plugin.claude.json +27 -5
- package/registry/generated/plugin.codex.json +28 -6
- package/registry/generated/plugin.cursor.json +27 -5
- package/registry/generated/plugin.factory.json +27 -5
- package/registry/generated/skills-index.md +36 -14
- package/registry/generated/skills-registry.json +421 -124
- package/registry/generated/templates-registry.json +2 -2
- package/library/skills/host-authoritative-state/SKILL.md +0 -332
- package/library/skills/host-authoritative-state/resource.yaml +0 -28
- package/library/skills/peer-to-peer-multiplayer/SKILL.md +0 -373
- package/library/skills/peer-to-peer-multiplayer/resource.yaml +0 -28
- package/library/skills/setup-multiplayer/SKILL.md +0 -336
- package/library/skills/setup-multiplayer/resource.yaml +0 -28
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# 2D Action RPG - Content Systems
|
|
2
|
+
|
|
3
|
+
The content layer on top of `2d-rpg.md`: enemies, NPCs and dialogue, quests,
|
|
4
|
+
inventory, loot, perks, status effects, and save/load. Everything here talks
|
|
5
|
+
to the rest of the game only through EventBus and the autoloads.
|
|
6
|
+
|
|
7
|
+
## Enemies (data-driven, five archetypes)
|
|
8
|
+
|
|
9
|
+
EnemyData resource defines stats + behavior; enemy_base.gd implements all
|
|
10
|
+
five behaviors and reads the data. No per-enemy scenes.
|
|
11
|
+
|
|
12
|
+
```gdscript
|
|
13
|
+
enum BehaviorType { CHASE, RANGED, SWARM, TANK, BOSS }
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- CHASE (slime): direct pursuit, melee lunge, low knockback resistance.
|
|
17
|
+
- RANGED (skeleton archer): holds preferred distance, fires projectiles,
|
|
18
|
+
strafes at range, backs off when crowded.
|
|
19
|
+
- SWARM (bat): fast, random jitter movement, contact damage.
|
|
20
|
+
- TANK (golem): slow, high HP, knockback_resistance 0.8, ground slam with
|
|
21
|
+
screen shake.
|
|
22
|
+
- BOSS (dark mage): cycling pattern (single shot -> fan of 3 -> charge),
|
|
23
|
+
phase 2 at 40% HP (faster everything + aura), can summon minions. Emits
|
|
24
|
+
`boss_health_changed` for the HUD boss bar.
|
|
25
|
+
|
|
26
|
+
EnemyData fields that matter for tuning: max_health, contact_damage,
|
|
27
|
+
move_speed, detection_range 200, attack_range 35, attack_cooldown 1.2,
|
|
28
|
+
knockback_resistance, xp_value, gold_min/max.
|
|
29
|
+
|
|
30
|
+
EnemyDatabase registers everything in code (no .tres files):
|
|
31
|
+
`db.get_enemy("slime")`, `db.get_enemies_for_wave(5)` returns a
|
|
32
|
+
wave-appropriate mix. Built-ins: slime, skeleton_archer, bat, golem,
|
|
33
|
+
dark_mage.
|
|
34
|
+
|
|
35
|
+
Contract: every enemy is in the "enemies" group and exposes
|
|
36
|
+
`take_hit(damage: int, source_position: Vector2, is_crit: bool = false)`.
|
|
37
|
+
Weapons and projectiles call it directly. Death drops XP + gold orbs via
|
|
38
|
+
`_drop_loot()` and emits `EventBus.enemy_killed(enemy, xp_value)`.
|
|
39
|
+
|
|
40
|
+
EnemySpawner is wave-based: `wave_interval` 12s, `enemies_per_wave_base` 3,
|
|
41
|
+
`enemies_per_wave_growth` 1, `max_enemies` 20, `difficulty_scale` 1.08
|
|
42
|
+
(HP/damage multiplier per wave). `spawn_boss("dark_mage", pos)` for arenas.
|
|
43
|
+
|
|
44
|
+
## NPCs and dialogue
|
|
45
|
+
|
|
46
|
+
DialogueData is a resource with a speaker name and a lines array. Each line
|
|
47
|
+
is a dictionary supporting optional keys:
|
|
48
|
+
|
|
49
|
+
```gdscript
|
|
50
|
+
{
|
|
51
|
+
"text": "Hello!",
|
|
52
|
+
"speaker": "Elder", # override
|
|
53
|
+
"choices": [{"text": "Yes", "next": 2}], # branching
|
|
54
|
+
"action": "start_quest:slay_monsters",
|
|
55
|
+
"condition": "quest_complete:slay_monsters"
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Actions: `start_quest:<id>`, `give_item:<resource_path>`, `give_gold:<n>`,
|
|
60
|
+
`heal`. Conditions: `quest_complete:<id>`, `quest_active:<id>`,
|
|
61
|
+
`has_item:<id>`. This tiny string DSL covers most RPG dialogue needs -
|
|
62
|
+
resist building a graph editor.
|
|
63
|
+
|
|
64
|
+
Interaction flow (the pause dance):
|
|
65
|
+
|
|
66
|
+
```gdscript
|
|
67
|
+
func interact(player: Node2D) -> void:
|
|
68
|
+
EventBus.dialogue_started.emit(npc_name)
|
|
69
|
+
GameManager.change_state(GameManager.GameState.DIALOGUE) # pauses tree
|
|
70
|
+
await _run_dialogue(start_index)
|
|
71
|
+
EventBus.dialogue_ended.emit()
|
|
72
|
+
GameManager.change_state(GameManager.GameState.PLAYING)
|
|
73
|
+
player.end_interaction()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
NPC = CharacterBody2D + CollisionShape2D + child Area2D named EXACTLY
|
|
77
|
+
"InteractionArea" on the interactions layer. Player entering shows a prompt;
|
|
78
|
+
interact key starts dialogue. When building NPCs in code, add the node tree
|
|
79
|
+
first, `add_child` it, THEN set script properties (npc_name, dialogue,
|
|
80
|
+
quest_to_give) - properties set before the script enters the tree can be
|
|
81
|
+
stomped by `_ready`.
|
|
82
|
+
|
|
83
|
+
DialogueBox: typewriter at 40 chars/sec, click skips to full text, click
|
|
84
|
+
again advances, choices render as buttons. Must be process_mode ALWAYS (tree
|
|
85
|
+
is paused during dialogue).
|
|
86
|
+
|
|
87
|
+
## Quests
|
|
88
|
+
|
|
89
|
+
QuestData: id, title, description, objectives array
|
|
90
|
+
(`{"id": "kill_slimes", "description": "Kill 3 slimes", "target": 3}`),
|
|
91
|
+
xp_reward, gold_reward, prerequisite_quest_id.
|
|
92
|
+
|
|
93
|
+
QuestManager (autoload) API: `start_quest(data)`,
|
|
94
|
+
`progress_objective(quest_id, objective_id, amount = 1)`,
|
|
95
|
+
`is_quest_active/complete(id)`, `get_objective_progress(id, obj_id)`. When
|
|
96
|
+
all objectives hit target the quest auto-completes, grants rewards, and
|
|
97
|
+
emits `quest_completed`.
|
|
98
|
+
|
|
99
|
+
Wiring kills to quests happens in the LEVEL script, not the enemy:
|
|
100
|
+
|
|
101
|
+
```gdscript
|
|
102
|
+
func _ready() -> void:
|
|
103
|
+
EventBus.enemy_killed.connect(_on_enemy_killed)
|
|
104
|
+
|
|
105
|
+
func _on_enemy_killed(_enemy: Node2D, _xp: int) -> void:
|
|
106
|
+
QuestManager.progress_objective("slay_monsters", "kill_enemies")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Enemies stay dumb; levels decide what counts.
|
|
110
|
+
|
|
111
|
+
## Inventory and items
|
|
112
|
+
|
|
113
|
+
InventoryManager (autoload): 20 item slots, 3 equipment slots (weapon,
|
|
114
|
+
armor, accessory), gold. `add_item()` returns false when full;
|
|
115
|
+
`use_item()` only works on consumables; `equip_item()` auto-routes by the
|
|
116
|
+
item's slot; `get_total_stat_bonus("attack")` sums equipment bonuses for the
|
|
117
|
+
stats pipeline; `clear()` on restart.
|
|
118
|
+
|
|
119
|
+
ItemData: ItemType (CONSUMABLE/WEAPON/ARMOR/ACCESSORY/KEY_ITEM), Rarity
|
|
120
|
+
(COMMON/UNCOMMON/RARE/EPIC with white/green/blue/purple colors),
|
|
121
|
+
`stat_bonuses: Dictionary` ("attack" -> 5), heal_amount, buy/sell price.
|
|
122
|
+
|
|
123
|
+
## Loot
|
|
124
|
+
|
|
125
|
+
- LootTable: guaranteed gold range + guaranteed XP, then `drop_chance` roll
|
|
126
|
+
over weighted entries (`{"item", "weight", "min_count", "max_count"}`).
|
|
127
|
+
`table.roll()` returns the drops.
|
|
128
|
+
- PickupOrb: XP/gold orbs from dead enemies, magnet-pull within 100px,
|
|
129
|
+
accelerating toward the player.
|
|
130
|
+
- TreasureChest: interactable static body - guaranteed item + gold orbs +
|
|
131
|
+
a loot table roll on open.
|
|
132
|
+
- ItemDrop: dropped items render with a rarity glow.
|
|
133
|
+
|
|
134
|
+
## Perks (level-up progression)
|
|
135
|
+
|
|
136
|
+
PerkData: id, name, description (supports a `{value}` token), max_level 3,
|
|
137
|
+
`stat_modifiers: Dictionary`, `special_effect: String` + effect_value for
|
|
138
|
+
non-stat perks. Built-in pool:
|
|
139
|
+
|
|
140
|
+
| ID | Effect per level |
|
|
141
|
+
|----|------|
|
|
142
|
+
| attack_up | +3 attack |
|
|
143
|
+
| speed_boost | +20 speed |
|
|
144
|
+
| thick_skin | +2 defense |
|
|
145
|
+
| vampirism | 3% lifesteal |
|
|
146
|
+
| burning_strikes | apply burn on hit |
|
|
147
|
+
| frost_touch | 30% slow on hit |
|
|
148
|
+
| critical_eye | +5% crit |
|
|
149
|
+
| multishot | +1 projectile |
|
|
150
|
+
| thorns | 20% reflect |
|
|
151
|
+
| regeneration | 1 HP/sec |
|
|
152
|
+
| xp_boost | +25% XP |
|
|
153
|
+
| last_stand | +50% damage below 30% HP |
|
|
154
|
+
|
|
155
|
+
PerkPool: `roll_selection(3)` (unowned or upgradable only),
|
|
156
|
+
`acquire_perk()`, `get_stat_bonus("attack")`, `has_effect("lifesteal")` +
|
|
157
|
+
`get_effect_value()`, `reset()` per run.
|
|
158
|
+
|
|
159
|
+
Level-up flow: `stats.add_xp()` true -> emit `player_leveled_up` then
|
|
160
|
+
`level_up_ready` -> PerkSelectionUI shows 3 cards (LEVEL_UP state pauses
|
|
161
|
+
tree) -> pick emits `perk_selected` -> player acquires and applies stat
|
|
162
|
+
modifiers via `stats.add_modifier` -> resume. Special effects (lifesteal,
|
|
163
|
+
thorns, multishot) are checked at their point of use via
|
|
164
|
+
`pool.has_effect()`, not applied as stats.
|
|
165
|
+
|
|
166
|
+
## Status effects
|
|
167
|
+
|
|
168
|
+
StatusEffect base (Node): effect_id, duration 3s, tick_interval 1s,
|
|
169
|
+
max_stacks 3. Override `tick()`, `on_expire()`, `on_max_stacks()`,
|
|
170
|
+
`get_speed_multiplier()`. `add_stack()` refreshes duration.
|
|
171
|
+
|
|
172
|
+
EffectManager attaches to any entity: `apply_effect(BurnEffect, entity)`
|
|
173
|
+
handles stacking, `get_speed_multiplier()` returns the product of all active
|
|
174
|
+
effects (feed it into movement), `remove_effect/remove_all`.
|
|
175
|
+
|
|
176
|
+
Built-ins and their max-stack payoffs (the interesting design bit -
|
|
177
|
+
stacking to cap triggers a burst):
|
|
178
|
+
|
|
179
|
+
- Burn: 3 dmg per 0.5s, 5 stacks; at cap, a 15x damage burst.
|
|
180
|
+
- Freeze: 30% slow per stack, 3 stacks; at cap, full 2s stun.
|
|
181
|
+
- Poison: 2 dmg/s, 3 stacks; each new stack extends duration 2s; supports
|
|
182
|
+
lifesteal back to the source.
|
|
183
|
+
|
|
184
|
+
## Juice quick reference
|
|
185
|
+
|
|
186
|
+
```gdscript
|
|
187
|
+
Juice.shake(4.0) # camera shake
|
|
188
|
+
Juice.hitstop(0.05) # freeze frame on big hits
|
|
189
|
+
Juice.flash_node(enemy, Color.WHITE, 0.1)
|
|
190
|
+
Juice.spawn_float_text(node, "+50 XP", Color.GOLD)
|
|
191
|
+
Juice.spawn_hit_particles(pos, Color.RED, 6)
|
|
192
|
+
Juice.bounce(node, 1.2) # scale bounce
|
|
193
|
+
Juice.register_camera(camera) # once, from player _ready()
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
If `register_camera` is never called, shake silently does nothing - a
|
|
197
|
+
classic "why is there no juice" bug.
|
|
198
|
+
|
|
199
|
+
## HUD and notifications
|
|
200
|
+
|
|
201
|
+
HUD subscribes to EventBus only: health/energy/XP bars, level, gold, active
|
|
202
|
+
quest list, and a notification label (fade in, 2s hold, fade out) driven by
|
|
203
|
+
`EventBus.notification_requested.emit("Quest complete!", Color.GOLD)`.
|
|
204
|
+
HUD also owns pause and inventory toggles.
|
|
205
|
+
|
|
206
|
+
## Audio
|
|
207
|
+
|
|
208
|
+
AudioManager: `play_music(path)` crossfades (pass false for hard cut),
|
|
209
|
+
`play_sfx(stream, pitch_variation)`, `play_sfx_at(stream, pos, variation)`
|
|
210
|
+
positional. Auto-creates Music and SFX buses if missing. Town theme in the
|
|
211
|
+
hub, battle music in combat areas, switch on `scene_ready`.
|
|
212
|
+
|
|
213
|
+
## Save/load
|
|
214
|
+
|
|
215
|
+
GameManager saves to `user://savegame.cfg` (ConfigFile): current level path
|
|
216
|
+
+ spawn position, player stats (level, XP, health, attack, defense),
|
|
217
|
+
inventory + equipment as resource paths, gold, quest state (active with
|
|
218
|
+
progress, completed ids). `save_game()` from the pause menu,
|
|
219
|
+
`load_game() -> bool` from Continue, `has_save()` gates the button.
|
|
220
|
+
`restart_game()` clears inventory, resets perks, returns to town.
|
|
221
|
+
|
|
222
|
+
Save items by resource path, not serialized objects - paths survive schema
|
|
223
|
+
changes; serialized Resources do not.
|
|
224
|
+
|
|
225
|
+
## Order of content work
|
|
226
|
+
|
|
227
|
+
1. Enemy roster (CHASE first, then RANGED; TANK/SWARM/BOSS after combat
|
|
228
|
+
feels right) + spawner in the forest level.
|
|
229
|
+
2. Loot orbs + level-up perks (progression pull).
|
|
230
|
+
3. One NPC with dialogue in town, one kill quest wired through the level.
|
|
231
|
+
4. Inventory + a treasure chest + equipment stat bonuses.
|
|
232
|
+
5. Status effects on weapons/perks (burning_strikes, frost_touch).
|
|
233
|
+
6. Boss + dungeon kill gate, then save/load last - it serializes everything
|
|
234
|
+
above, so build it when the shapes are stable.
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# 2D Action RPG - Core
|
|
2
|
+
|
|
3
|
+
Top-down action RPG blueprint: event-driven architecture, player controller,
|
|
4
|
+
stats pipeline, combat components, weapons, and area transitions. NPCs,
|
|
5
|
+
dialogue, quests, inventory, perks, status effects, and enemies live in
|
|
6
|
+
`2d-rpg-content.md`.
|
|
7
|
+
|
|
8
|
+
## Minimum playable base (build this first)
|
|
9
|
+
|
|
10
|
+
One level extending LevelBase: player with 8-way movement + one sword swing +
|
|
11
|
+
dodge roll, a couple of chase enemies with `take_hit()`, HUD health bar. Prove
|
|
12
|
+
"move, hit enemy, get hit, see numbers" before any dialogue or quest exists.
|
|
13
|
+
|
|
14
|
+
Two rules for every scene:
|
|
15
|
+
|
|
16
|
+
- Dynamic viewport: `display/window/stretch/mode = "canvas_items"`,
|
|
17
|
+
`stretch/aspect = "expand"`. UI via Control anchors; compute spawn points and
|
|
18
|
+
bounds from `get_viewport_rect().size`, never hardcoded 1920x1080.
|
|
19
|
+
- Playable slice: every input gets feedback through the Juice autoload (shake,
|
|
20
|
+
flash, float text, hitstop). A silent build fails even if every system works.
|
|
21
|
+
|
|
22
|
+
## Architecture: modules + EventBus
|
|
23
|
+
|
|
24
|
+
Systems NEVER reference each other directly. All cross-system communication
|
|
25
|
+
flows through EventBus, a global signal bus autoload. Each module can be added
|
|
26
|
+
or removed without breaking others.
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
core/
|
|
30
|
+
autoloads/{event_bus,game_manager}.gd
|
|
31
|
+
levels/level_base.gd
|
|
32
|
+
player/{player.gd,player.tscn}
|
|
33
|
+
modules/
|
|
34
|
+
audio/audio_manager.gd
|
|
35
|
+
combat/{hitbox_component,hurtbox_component,knockback_component,
|
|
36
|
+
stats,modified_value}.gd
|
|
37
|
+
dialogue/{dialogue_box,dialogue_data,npc}.gd
|
|
38
|
+
enemies/{enemy_base,enemy_data,enemy_database,enemy_spawner}.gd
|
|
39
|
+
effects/{status_effect,effect_manager,burn_effect,freeze_effect,poison_effect}.gd
|
|
40
|
+
inventory/{inventory_manager,inventory_ui,item_data}.gd
|
|
41
|
+
juice/juice.gd
|
|
42
|
+
loot/{area_transition,item_drop,loot_table,pickup_orb,treasure_chest}.gd
|
|
43
|
+
progression/{perk_data,perk_database,perk_pool,perk_selection_ui}.gd
|
|
44
|
+
quests/{quest_data,quest_manager}.gd
|
|
45
|
+
weapons/{weapon_base,weapon_data,weapon_manager,projectile}.gd
|
|
46
|
+
weapons/weapons/{sword,bow,staff,orb}.gd
|
|
47
|
+
content/levels/{town,forest,dungeon}.gd
|
|
48
|
+
ui/{hud/hud.gd, menus/{main_menu,pause_menu,game_over}.gd}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Six autoloads, in this order: EventBus, GameManager, AudioManager,
|
|
52
|
+
QuestManager, Juice, InventoryManager.
|
|
53
|
+
|
|
54
|
+
### EventBus signal groups (define up front)
|
|
55
|
+
|
|
56
|
+
Player: `player_health_changed`, `player_energy_changed`, `player_died`,
|
|
57
|
+
`player_leveled_up`, `player_xp_gained`. Combat: `damage_dealt`,
|
|
58
|
+
`enemy_killed(enemy, xp_value)`, `player_attack_executed`. Interaction:
|
|
59
|
+
`interaction_available/unavailable`, `dialogue_started/ended`. Inventory:
|
|
60
|
+
`item_picked_up/used/equipped/unequipped`, `gold_changed`. Quests:
|
|
61
|
+
`quest_started/updated/completed`, `quest_objective_progressed`. Progression:
|
|
62
|
+
`level_up_ready`, `perk_selected`, `perk_applied`. Scene:
|
|
63
|
+
`scene_transition_started/completed`, `scene_ready`. Spawner:
|
|
64
|
+
`wave_started/cleared`. UI: `notification_requested(text, color)`,
|
|
65
|
+
`boss_health_changed`.
|
|
66
|
+
|
|
67
|
+
## Physics layers and input
|
|
68
|
+
|
|
69
|
+
| Layer | Name |
|
|
70
|
+
|-------|------|
|
|
71
|
+
| 1 | terrain |
|
|
72
|
+
| 2 | player |
|
|
73
|
+
| 3 | enemies |
|
|
74
|
+
| 4 | player_hitbox |
|
|
75
|
+
| 5 | enemy_hitbox |
|
|
76
|
+
| 6 | interactions |
|
|
77
|
+
| 7 | pickups |
|
|
78
|
+
|
|
79
|
+
Hitbox on 4/5, hurtbox monitors the opposite side's layer.
|
|
80
|
+
|
|
81
|
+
Input map (9 actions): move_up/down/left/right (WASD + arrows + stick), attack
|
|
82
|
+
(LMB/Space), dodge (RMB/Shift), interact (E), pause (Esc), inventory (I).
|
|
83
|
+
|
|
84
|
+
## GameManager: states and transitions
|
|
85
|
+
|
|
86
|
+
```gdscript
|
|
87
|
+
enum GameState { MAIN_MENU, PLAYING, PAUSED, DIALOGUE, INVENTORY, LEVEL_UP, GAME_OVER }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Everything except MAIN_MENU/PLAYING pauses the tree; `change_state()` handles
|
|
91
|
+
pause/unpause. Gate gameplay input on `GameManager.is_gameplay_active()`.
|
|
92
|
+
|
|
93
|
+
Scene transition flow (fade overlay owned by GameManager):
|
|
94
|
+
`transition_to_scene(path, spawn_point, spawn_direction)` -> fade to black
|
|
95
|
+
0.3s -> emit `scene_transition_started` -> `change_scene_to_file` -> wait TWO
|
|
96
|
+
process frames -> emit `scene_ready`, state PLAYING -> fade in -> emit
|
|
97
|
+
`scene_transition_completed`. The player reads
|
|
98
|
+
`GameManager.player_spawn_point` / `player_spawn_direction` in `_ready()`.
|
|
99
|
+
Guard with an `_is_transitioning` flag; never call transition from inside a
|
|
100
|
+
transition callback.
|
|
101
|
+
|
|
102
|
+
Area transitions are trivial once this exists - an Area2D with three exports
|
|
103
|
+
(`target_scene`, `spawn_point`, `spawn_direction`) whose `body_entered` calls
|
|
104
|
+
`GameManager.transition_to_scene()`. Place at map edges and doorways.
|
|
105
|
+
|
|
106
|
+
## Player controller
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
Player (CharacterBody2D, layer 2, group "player")
|
|
110
|
+
CollisionShape2D (circle, radius 8)
|
|
111
|
+
WeaponManager (Node2D)
|
|
112
|
+
HurtboxComponent (Area2D)
|
|
113
|
+
CollisionShape2D (circle, radius 10)
|
|
114
|
+
InteractionArea (Area2D)
|
|
115
|
+
CollisionShape2D (circle, radius 30)
|
|
116
|
+
Camera2D
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
State machine: `IDLE, MOVE, ATTACK, DODGE, HURT, DEAD, INTERACT`.
|
|
120
|
+
|
|
121
|
+
```gdscript
|
|
122
|
+
var input := Input.get_vector("move_left", "move_right", "move_up", "move_down")
|
|
123
|
+
if input.length() > 0.1:
|
|
124
|
+
facing = input.normalized()
|
|
125
|
+
velocity = facing * stats.get_speed()
|
|
126
|
+
state = State.MOVE
|
|
127
|
+
else:
|
|
128
|
+
velocity = velocity.move_toward(Vector2.ZERO, stats.get_speed() * 10.0 * delta)
|
|
129
|
+
state = State.IDLE
|
|
130
|
+
move_and_slide()
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
- Attack: manual aims at mouse direction; optional auto_attack targets the
|
|
134
|
+
nearest enemy within 250px. Delegate to
|
|
135
|
+
`weapon_manager.attack(direction)` gated by `can_attack()`.
|
|
136
|
+
- Dodge roll: costs 20 energy (`stats.use_energy` returns false if broke),
|
|
137
|
+
0.25s duration, invincible for the duration, fading trail rects.
|
|
138
|
+
- Hurt: ignore if invincible/dead/dodging. `stats.take_damage`, 0.5s i-frames,
|
|
139
|
+
knockback velocity, `Juice.shake(5)`, red flash, float text, emit
|
|
140
|
+
`player_health_changed`.
|
|
141
|
+
|
|
142
|
+
## Stats: the ModifiedValue pipeline
|
|
143
|
+
|
|
144
|
+
`final = (base + additive_sum) * (1.0 + multiplicative_sum)`. Modifiers are
|
|
145
|
+
tagged with a source string so a whole buff can be removed in one call:
|
|
146
|
+
|
|
147
|
+
```gdscript
|
|
148
|
+
stats.add_modifier("attack", 5, "fire_buff")
|
|
149
|
+
stats.remove_modifier("attack", "fire_buff")
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Stats resource: base_max_health 100, base_attack 10, base_defense 5,
|
|
153
|
+
base_speed 120, base_crit_chance 0.05, base_crit_multiplier 1.5. Key methods:
|
|
154
|
+
`take_damage()` returns `{damage, killed}`, `calculate_damage(is_crit)`,
|
|
155
|
+
`roll_crit()`, `add_xp()` returns true on level-up, `use_energy()`.
|
|
156
|
+
|
|
157
|
+
MUST call `stats.initialize()` in `_ready()` - forgetting it leaves the
|
|
158
|
+
pipeline null and everything crashes on first hit.
|
|
159
|
+
|
|
160
|
+
Level-up: +10 max HP, +2 attack, +1 defense, +3 speed, full heal.
|
|
161
|
+
XP needed = `50 + level * 30`.
|
|
162
|
+
|
|
163
|
+
## Combat components (reusable trio)
|
|
164
|
+
|
|
165
|
+
- HitboxComponent (Area2D): the damage dealer. `damage`, `knockback_force`,
|
|
166
|
+
`hit_cooldown` (per-target cooldown dictionary), `try_hit(target) -> bool`,
|
|
167
|
+
`activate()/deactivate()` (deactivate clears cooldowns). Emits
|
|
168
|
+
`hit_landed(target, damage, knockback)`.
|
|
169
|
+
- HurtboxComponent (Area2D): the receiver. `invincibility_duration` 0.3s,
|
|
170
|
+
emits `damage_received(amount, source, knockback)` via
|
|
171
|
+
`take_hit(damage, source, knockback)`.
|
|
172
|
+
- KnockbackComponent (Node on any CharacterBody2D): decaying velocity,
|
|
173
|
+
`resistance` 0-1, `decay_rate` 8. `apply_knockback(force)` then add
|
|
174
|
+
`get_knockback_velocity()` to movement.
|
|
175
|
+
|
|
176
|
+
Attach these to player, enemies, chests - anything hittable. This trio is
|
|
177
|
+
what keeps combat code out of entity scripts.
|
|
178
|
+
|
|
179
|
+
## Weapon system (data-driven)
|
|
180
|
+
|
|
181
|
+
WeaponData resource carries everything: type string, base_damage,
|
|
182
|
+
attack_speed (attacks/sec), knockback, range, piercing, projectile
|
|
183
|
+
speed/count/fan_angle, area_radius, orbit_count/speed, color. WeaponManager
|
|
184
|
+
(child of player) equips, switches (1-4 keys / scroll), and delegates attacks;
|
|
185
|
+
a `WEAPON_SCRIPTS` const maps type string -> preloaded script.
|
|
186
|
+
|
|
187
|
+
Four archetypes:
|
|
188
|
+
|
|
189
|
+
- Sword: 120-degree arc swing, alternating direction, ease-out for weight,
|
|
190
|
+
forward lunge, physics cone query, trail.
|
|
191
|
+
- Bow: 0.15s draw, then a fan of projectiles (count + fan_angle from data).
|
|
192
|
+
- Staff: homing bolts (homing_strength 3.0) + optional `trigger_nova()` AoE.
|
|
193
|
+
- Orb: passive - orbits the player, contact damage with per-enemy cooldown,
|
|
194
|
+
always active regardless of selected weapon.
|
|
195
|
+
|
|
196
|
+
One generic projectile script serves bow, staff, AND enemies: straight or
|
|
197
|
+
homing, piercing (tracks hit bodies to prevent double-hit), knockback, trail,
|
|
198
|
+
lifetime despawn. Configure per-shot via properties + `collision_mask` and a
|
|
199
|
+
`source_node` so it never hits its shooter.
|
|
200
|
+
|
|
201
|
+
## LevelBase and level authoring
|
|
202
|
+
|
|
203
|
+
All levels extend LevelBase, which does the boring wiring once:
|
|
204
|
+
|
|
205
|
+
```gdscript
|
|
206
|
+
func _ready() -> void:
|
|
207
|
+
_spawn_player() # instantiate player.tscn
|
|
208
|
+
_setup_ui() # HUD, PauseMenu, DialogueBox, Inventory, GameOver, PerkSelectionUI
|
|
209
|
+
_setup_connections()
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
A concrete level sets `level_name`, calls `super._ready()`, then adds its
|
|
213
|
+
spawner / NPCs / AreaTransitions. Town = safe hub with NPCs. Forest = combat
|
|
214
|
+
area with wave spawner. Dungeon = boss arena with a kill gate.
|
|
215
|
+
|
|
216
|
+
## Build order
|
|
217
|
+
|
|
218
|
+
1. project.godot: 6 autoloads in order, input map, layer names, 1920x1080
|
|
219
|
+
canvas_items/expand.
|
|
220
|
+
2. EventBus with the full signal catalog (empty handlers are fine).
|
|
221
|
+
3. Stats + ModifiedValue, then the combat component trio.
|
|
222
|
+
4. Player: movement -> dodge -> WeaponManager with sword only.
|
|
223
|
+
5. One chase enemy + Juice feedback + HUD health bar. Playable slice check.
|
|
224
|
+
6. GameManager transitions + LevelBase + a second area with AreaTransition.
|
|
225
|
+
7. Content systems from `2d-rpg-content.md`: enemies, NPCs, quests,
|
|
226
|
+
inventory, perks, effects, save/load.
|
|
227
|
+
|
|
228
|
+
## Traps
|
|
229
|
+
|
|
230
|
+
- Direct module references. Never `get_node("/root/QuestManager")` from a
|
|
231
|
+
weapon script - emit an EventBus signal. Modules only know EventBus and
|
|
232
|
+
their own data types; anything else rots into circular dependencies.
|
|
233
|
+
- Skipping `stats.initialize()`. Null pipeline, crash on first damage.
|
|
234
|
+
- Huge `_ready()` chains needing other nodes. Await
|
|
235
|
+
`get_tree().process_frame` or connect to `EventBus.scene_ready` instead.
|
|
236
|
+
- UI without `process_mode = ALWAYS`. Dialogue, inventory, level-up, and
|
|
237
|
+
pause all pause the tree; the UI must keep processing.
|
|
238
|
+
- Wrong hitbox/hurtbox layers. Hitbox layer 4/5, hurtbox masks the OPPOSING
|
|
239
|
+
side. Getting this backwards silently produces no hits.
|
|
240
|
+
- Renaming InteractionArea. NPC scripts access `$InteractionArea` by exact
|
|
241
|
+
name; a rename kills interaction with no error at parse time.
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# 2D Survivors - Progression
|
|
2
|
+
|
|
3
|
+
The systems layer on top of `2d-survivors.md`: enemy roster, wave-driven
|
|
4
|
+
spawning, elites, level-up upgrades, the between-wave shop, weapon evolution,
|
|
5
|
+
and characters. Build these only after the core loop (move + auto-fire + chase)
|
|
6
|
+
runs.
|
|
7
|
+
|
|
8
|
+
## Enemy roster (one script, many behaviors)
|
|
9
|
+
|
|
10
|
+
All types share `enemy_base.gd` (Area2D, "enemies" group), configured through
|
|
11
|
+
`setup(type, wave)` - no per-type scenes. HP/speed scale with wave number:
|
|
12
|
+
|
|
13
|
+
| Type | Behavior | Health | Speed | Special |
|
|
14
|
+
|---|---|---|---|---|
|
|
15
|
+
| Walker | chase + wobble | 15 + wave*8 | 55 + wave*2 | standard horde unit |
|
|
16
|
+
| Runner | fast chase | 8 + wave*4 | 110 + wave*3 | 0.7x scale |
|
|
17
|
+
| Tank | slow, big | 50 + wave*15 | 35 + wave*1 | 1.5x scale, 3 XP |
|
|
18
|
+
| Boss | massive | 500 + wave*80 | 45 | 2.8x scale, 25 XP |
|
|
19
|
+
| Ranged | keeps distance | 12 + wave*5 | 50 + wave*1.5 | fires projectiles |
|
|
20
|
+
| Charger | telegraph + dash | 20 + wave*7 | 50 + wave*1.5 | 550px/s burst |
|
|
21
|
+
| Spawner | stays back | 80 + wave*20 | 30 | spawns 2-3 minions |
|
|
22
|
+
|
|
23
|
+
Behavior dispatch is a `match behavior:` in `_physics_process`. Give every
|
|
24
|
+
enemy a randomized wobble (`_wobble_phase = randf() * TAU`, per-enemy speed and
|
|
25
|
+
amplitude) so hordes look organic instead of converging in a straight line.
|
|
26
|
+
|
|
27
|
+
Charger is a 4-state machine: WALK -> TELEGRAPH (0.45s, within 320px trigger
|
|
28
|
+
range) -> CHARGE (0.4s at 550px/s in the locked direction) -> RECOVER (0.3s),
|
|
29
|
+
then 3.5s cooldown. The telegraph is what makes it fair - flash/scale the sprite
|
|
30
|
+
during it.
|
|
31
|
+
|
|
32
|
+
Knockback: on hit, set `_knockback_vel = (pos - source).normalized() * 180`,
|
|
33
|
+
decay with `move_toward(Vector2.ZERO, 900 * delta)` and add to movement.
|
|
34
|
+
|
|
35
|
+
### Hit feedback and death (non-negotiable)
|
|
36
|
+
|
|
37
|
+
`take_damage(amount, source_pos, is_crit)` must: flash the sprite, spawn a
|
|
38
|
+
floating damage number (bigger + colored on crit), spawn directional particles,
|
|
39
|
+
apply knockback, show the health bar. Death: `GameManager.add_kill()`,
|
|
40
|
+
`add_xp(xp_value)`, drop gold (rare health orb), particle burst, then disable
|
|
41
|
+
before animating out:
|
|
42
|
+
|
|
43
|
+
```gdscript
|
|
44
|
+
set_physics_process(false)
|
|
45
|
+
set_deferred("monitoring", false)
|
|
46
|
+
set_deferred("monitorable", false)
|
|
47
|
+
var tween := create_tween().set_parallel(true)
|
|
48
|
+
tween.tween_property(self, "scale", scale * 1.5, 0.1)
|
|
49
|
+
tween.tween_property(self, "modulate:a", 0.0, 0.1)
|
|
50
|
+
tween.chain().tween_callback(queue_free)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The `set_deferred` pair matters: freeing an Area2D mid-physics-callback without
|
|
54
|
+
disabling monitoring first throws errors.
|
|
55
|
+
|
|
56
|
+
## Spawner and wave data
|
|
57
|
+
|
|
58
|
+
The spawner reads a per-wave config and spawns groups on an interval, always
|
|
59
|
+
off-screen (650-900px from the player, clamped to arena bounds):
|
|
60
|
+
|
|
61
|
+
```gdscript
|
|
62
|
+
func _random_spawn_pos() -> Vector2:
|
|
63
|
+
var angle := randf() * TAU
|
|
64
|
+
var dist := randf_range(650.0, 900.0)
|
|
65
|
+
var pos := GameManager.player_node.global_position + Vector2.from_angle(angle) * dist
|
|
66
|
+
pos.x = clampf(pos.x, -ARENA_HALF_SIZE, ARENA_HALF_SIZE)
|
|
67
|
+
pos.y = clampf(pos.y, -ARENA_HALF_SIZE, ARENA_HALF_SIZE)
|
|
68
|
+
return pos
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Each WaveConfig: `enemy_weights: Dictionary`, `spawn_interval`, `group_size`,
|
|
72
|
+
`max_alive`, `elite_chance`, `boss: bool`, `hazards: int`, `duration`. Type
|
|
73
|
+
selection is weighted random over the dictionary. Hand-tune 20 waves; the ramp
|
|
74
|
+
that matters:
|
|
75
|
+
|
|
76
|
+
| Wave | Types | Interval | Group | Max alive | Elite % | Boss | Duration |
|
|
77
|
+
|---|---|---|---|---|---|---|---|
|
|
78
|
+
| 1 | walker | 2.5s | 2 | 25 | 0% | no | 20s |
|
|
79
|
+
| 3 | +runner | 2.0s | 4 | 45 | 0% | no | 20s |
|
|
80
|
+
| 5 | +charger | 1.7s | 5 | 65 | 3% | no | 22s |
|
|
81
|
+
| 8 | +tank +ranged | 1.3s | 8 | 100 | 6% | no | 22s |
|
|
82
|
+
| 10 | all 6 | 1.1s | 10 | 120 | 8% | YES | 30s |
|
|
83
|
+
| 15 | tank heavy | 0.75s | 15 | 190 | 13% | no | 28s |
|
|
84
|
+
| 20 | max density | 0.45s | 22 | 250 | 20% | YES | 30s |
|
|
85
|
+
|
|
86
|
+
Do NOT scale difficulty by HP alone. The ramp is spawn rate + group size +
|
|
87
|
+
max alive + type diversity + elite chance + new mechanics at fixed waves.
|
|
88
|
+
Bosses spawn 3s into their combat phase via a scene-tree timer.
|
|
89
|
+
|
|
90
|
+
Arena hazards from wave 3+: spike traps (static) and fire pools (drift at
|
|
91
|
+
15-35px/s). Tick damage every 0.6s (6 + wave*1.5) to enemies in radius, half
|
|
92
|
+
damage to the player - positioning pressure for both sides.
|
|
93
|
+
|
|
94
|
+
## Elites
|
|
95
|
+
|
|
96
|
+
`elite_system.gd` is a static RefCounted helper applied at spawn time with
|
|
97
|
+
`elite_chance` probability. One of four modifiers, 3x XP:
|
|
98
|
+
|
|
99
|
+
- FAST: speed x1.8, HP x1.3, lightened color
|
|
100
|
+
- TANKY: HP x3, scale x1.3, contact damage x1.5, darkened
|
|
101
|
+
- SPLITTING: HP x1.5, green; on death spawns 2 walker copies at 0.6x scale,
|
|
102
|
+
0.3x HP (use `call_deferred("add_child", copy)` - you are inside a physics
|
|
103
|
+
callback)
|
|
104
|
+
- VAMPIRIC: HP x2; heals 8% max HP every 3s
|
|
105
|
+
|
|
106
|
+
Give elites a visible pulse tween so players can prioritize them.
|
|
107
|
+
|
|
108
|
+
## Two-track progression
|
|
109
|
+
|
|
110
|
+
Track 1 - mid-wave level-ups: on level-up the tree pauses, 3 random stat
|
|
111
|
+
upgrades from the pool are offered, each stackable to level 3. Also vacuum all
|
|
112
|
+
pickups (`attract()` on the "pickups" group) as a reward beat.
|
|
113
|
+
|
|
114
|
+
| Upgrade | Per level | Stat touched |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| Might | +15% damage | player_damage_mult |
|
|
117
|
+
| Haste | +12% attack speed | player_attack_speed_mult |
|
|
118
|
+
| Swiftness | +20 speed | player_speed |
|
|
119
|
+
| Vitality | +15 max HP (and heal 15) | player_max_health |
|
|
120
|
+
| Magnetism | +20 pickup range | player_pickup_range |
|
|
121
|
+
| Armor | +1 flat reduction | player_armor |
|
|
122
|
+
| Regeneration | +0.5 HP/s | player_regen |
|
|
123
|
+
| Critical Eye | +5% crit | player_crit_chance |
|
|
124
|
+
|
|
125
|
+
`get_random_choices(3)` filters out maxed upgrades, shuffles, takes 3.
|
|
126
|
+
`apply_upgrade` bumps level and writes the stat straight onto GameManager.
|
|
127
|
+
Armor applies as `maxi(1, amount - player_armor)` - always at least 1 damage.
|
|
128
|
+
|
|
129
|
+
Track 2 - between-wave shop: 4 random items, gold-priced, 4 tiers (Common /
|
|
130
|
+
Uncommon / Rare / Legendary). Tier odds shift with wave - by wave 16+:
|
|
131
|
+
15% Legendary / 30% Rare / 35% Uncommon / 20% Common; waves 1-5 have no
|
|
132
|
+
Legendary at all. Prices scale `cost_base * (1.0 + wave * 0.12)`; reroll costs
|
|
133
|
+
`2 + reroll_count + wave / 5` (escalates within a single shop visit).
|
|
134
|
+
|
|
135
|
+
Items are either stat dictionaries applied to GameManager
|
|
136
|
+
(`{"player_armor": 2}`) or weapon grants that call
|
|
137
|
+
`player.add_or_upgrade_weapon(id)`. The interesting ones trade off:
|
|
138
|
+
|
|
139
|
+
```gdscript
|
|
140
|
+
_add("glass_cannon", "Glass Cannon", 2, 40,
|
|
141
|
+
{"player_damage_mult": 0.35, "player_max_health": -25}, ["offense"])
|
|
142
|
+
_add("berserker_mask", "Berserker Mask", 2, 45,
|
|
143
|
+
{"player_damage_mult": 0.25, "player_attack_speed_mult": 0.20,
|
|
144
|
+
"player_armor": -3}, ["offense"])
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Do not skip the shop. Without a between-wave decision point the game is just
|
|
148
|
+
"survive longer"; trade-off items are what create builds.
|
|
149
|
+
|
|
150
|
+
## Weapon evolution
|
|
151
|
+
|
|
152
|
+
A base weapon evolves when the player owns it AND a specific catalyst upgrade
|
|
153
|
+
is maxed (level 3). Check after every upgrade pick and shop purchase; emit
|
|
154
|
+
`evolution_available` once per recipe:
|
|
155
|
+
|
|
156
|
+
| Evolved | Base weapon | Catalyst | Effect |
|
|
157
|
+
|---|---|---|---|
|
|
158
|
+
| Piercing Barrage | Projectile | Multi Shot | pierces 4+, faster bullets |
|
|
159
|
+
| Radiant Barrier | Orbital | Armor | 4 large orbitals + damage pulses |
|
|
160
|
+
| Supernova | Holy Nova | Might | bigger AoE + burning ground DOT |
|
|
161
|
+
|
|
162
|
+
Evolving replaces the node: `queue_free()` the old weapon, erase from the
|
|
163
|
+
player's `_weapons` dict, instantiate the evolved script, screen flash + zoom
|
|
164
|
+
pulse. DOT zones (Supernova) are a Polygon2D + tween loop: N tick callbacks at
|
|
165
|
+
0.5s intervals doing a group distance scan, then fade and free. Guard every
|
|
166
|
+
tick with `is_instance_valid` - the zone can outlive enemies and vice versa.
|
|
167
|
+
|
|
168
|
+
## Characters
|
|
169
|
+
|
|
170
|
+
Four picks before the run, each a starting weapon + stat override dictionary:
|
|
171
|
+
|
|
172
|
+
- Sentinel: projectile, no overrides (baseline)
|
|
173
|
+
- Berserker: projectile, damage_mult 1.4, speed 240, max HP 80
|
|
174
|
+
- Warden: orbital, max HP 150, armor 3, attack_speed 0.85
|
|
175
|
+
- Arcanist: nova, attack_speed 1.25, damage_mult 1.15, max HP 70
|
|
176
|
+
|
|
177
|
+
`GameManager.reset_run()` resets all stats to base, then applies
|
|
178
|
+
`selected_character.stat_overrides` via `set(key, value)` and fills health to
|
|
179
|
+
max. Character select is just 4 cards that set `selected_character` and load
|
|
180
|
+
the arena.
|
|
181
|
+
|
|
182
|
+
## UI stack (all procedural, all CanvasLayer, all process_mode ALWAYS)
|
|
183
|
+
|
|
184
|
+
- HUD: health + dash bars top-left, wave counter + timer top-center,
|
|
185
|
+
gold/kills/level top-right, full-width 6px XP bar at the bottom.
|
|
186
|
+
- LevelUpUI (layer 20): dark overlay, 3 cards, gamepad focus.
|
|
187
|
+
- ShopUI (layer 25): stats panel left, 4 tier-colored item cards, reroll +
|
|
188
|
+
next-wave buttons, "SOLD" placeholders, live stat refresh after purchase.
|
|
189
|
+
- PauseMenu / GameOverUI (layer 40): game over shows victory or death plus
|
|
190
|
+
time / kills / level / wave, retry + menu.
|
|
191
|
+
|
|
192
|
+
Retry = `get_tree().paused = false` THEN `reload_current_scene()`. Forgetting
|
|
193
|
+
the unpause is the classic frozen-retry bug.
|
|
194
|
+
|
|
195
|
+
## Audio integration points
|
|
196
|
+
|
|
197
|
+
Pooled AudioManager (16 global + 16 positional players, round-robin; all play
|
|
198
|
+
methods null-safe so missing streams never crash). Pitch variance 5% global /
|
|
199
|
+
10% positional prevents machine-gun repetition. Hook: enemy hit + death
|
|
200
|
+
(positional), pickup collect, level-up, shop purchase, weapon fire (-6dB, it
|
|
201
|
+
fires constantly), music on combat start with fade.
|