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,186 @@
|
|
|
1
|
+
# 3D Horror - Systems
|
|
2
|
+
|
|
3
|
+
The dread machinery on top of `3d-horror.md`: the tension-driven atmosphere
|
|
4
|
+
controller, the stalker AI, procedural audio, interactables, and the escape
|
|
5
|
+
objective chain.
|
|
6
|
+
|
|
7
|
+
## Atmosphere controller (tension in, dread out)
|
|
8
|
+
|
|
9
|
+
One Node3D in the level reads `GameManager.tension_level` and escalates
|
|
10
|
+
three channels. It auto-discovers OmniLights from a sibling "Lights" node
|
|
11
|
+
and creates 4 roaming AudioStreamPlayer3D emitters (Ambient bus, max
|
|
12
|
+
distance 20).
|
|
13
|
+
|
|
14
|
+
### Flickering lights
|
|
15
|
+
|
|
16
|
+
Per-frame chance `0.005 * (1.0 + tension)` per light. A flicker is a tween
|
|
17
|
+
of 2-5 random off/on cycles (off 0.02-0.08s at energy 0-0.3, on 0.02-0.1s
|
|
18
|
+
at 50-100%), then restore. Random durations - regular flicker reads as a
|
|
19
|
+
broken script, not a broken light.
|
|
20
|
+
|
|
21
|
+
### Positional ambient sounds
|
|
22
|
+
|
|
23
|
+
Every 15-45s (interval shrinks to half at max tension), one emitter
|
|
24
|
+
teleports to a random point around the player
|
|
25
|
+
(x/z +-15, y 0-5 - behind walls, above the ceiling) and plays from a pool
|
|
26
|
+
that GROWS with tension:
|
|
27
|
+
|
|
28
|
+
- base: door close, pipe stress, wind vent
|
|
29
|
+
- tension > 0.3: + creature whisper
|
|
30
|
+
- tension > 0.5: + distant scream
|
|
31
|
+
- tension > 0.7: + creature growl
|
|
32
|
+
|
|
33
|
+
Random volume -12..-4 dB and pitch 0.7-1.3 so nothing repeats exactly.
|
|
34
|
+
|
|
35
|
+
### Tension events
|
|
36
|
+
|
|
37
|
+
Every 30s at base, down to ~12s at max tension, pick one of FIVE:
|
|
38
|
+
0 = flicker all lights, 1 = audio stinger + tension +0.05, 2 = brief
|
|
39
|
+
darkness, 3 = extra ambient sound, 4 = NOTHING. The nothing case is
|
|
40
|
+
deliberate - a scare that never comes after buildup is the horror.
|
|
41
|
+
|
|
42
|
+
Brief darkness: tween all lights to energy 0.05 over 0.5s, hold a random
|
|
43
|
+
0.5-2s, restore over 0.3s, tension +0.08. Save original energies first.
|
|
44
|
+
|
|
45
|
+
## Stalker AI
|
|
46
|
+
|
|
47
|
+
A solid-black unshaded capsule (CapsuleMesh r 0.2 h 2.2, albedo 0.01 -
|
|
48
|
+
reads as a silhouette in any lighting). Mesh and collision built at
|
|
49
|
+
runtime, no assets. Core mechanic: it only moves when you are NOT looking.
|
|
50
|
+
|
|
51
|
+
```gdscript
|
|
52
|
+
func _process_showing(delta: float) -> void:
|
|
53
|
+
look_at(Vector3(player.global_position.x, global_position.y, player.global_position.z))
|
|
54
|
+
var to_me := (global_position - player.global_position).normalized()
|
|
55
|
+
var player_fwd := -player.global_transform.basis.z.normalized()
|
|
56
|
+
var angle := rad_to_deg(acos(clampf(player_fwd.dot(to_me), -1.0, 1.0)))
|
|
57
|
+
if angle < 20.0:
|
|
58
|
+
# player looking directly: freeze and stare back
|
|
59
|
+
stare_timer += delta
|
|
60
|
+
if stare_timer >= stare_time: # 2.0s of being watched
|
|
61
|
+
_disappear()
|
|
62
|
+
else:
|
|
63
|
+
stare_timer = 0.0
|
|
64
|
+
var dir := (player.global_position - global_position).normalized()
|
|
65
|
+
dir.y = 0.0
|
|
66
|
+
velocity = dir * approach_speed # 1.5 m/s
|
|
67
|
+
move_and_slide()
|
|
68
|
+
if global_position.distance_to(player.global_position) < catch_distance: # 1.5m
|
|
69
|
+
AudioManager.play_sfx(ProceduralAudio.get_creature_screech(), 0.0)
|
|
70
|
+
GameManager.set_state(GameManager.GameState.GAME_OVER)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Pacing rules (tuned, keep them):
|
|
74
|
+
|
|
75
|
+
- First appearance at 15s - the player needs time to find the flashlight.
|
|
76
|
+
- Reappear interval starts 18s, shrinks 15% per appearance, floor 8s:
|
|
77
|
+
`reappear_interval = maxf(reappear_interval * 0.85, 8.0)`.
|
|
78
|
+
- Spawn selection: from a hand-placed spawn-point list, pick the FARTHEST
|
|
79
|
+
point that is between 6m and 18m from the player. Under 6m is unfair -
|
|
80
|
+
no time to react.
|
|
81
|
+
- On appear: whisper from its own 3D emitter + low-pitched stinger
|
|
82
|
+
(pitch 0.5-0.8) + tension +0.1.
|
|
83
|
+
- Fully idle while `GameManager.is_reading_note` or state != PLAYING -
|
|
84
|
+
notes are safe rooms.
|
|
85
|
+
- After `stare_time` of being watched it vanishes (visible = false) and
|
|
86
|
+
the wait timer restarts. Player-facing check is a dot-product cone of
|
|
87
|
+
20 degrees, not a raycast - cheap and good enough.
|
|
88
|
+
|
|
89
|
+
## Procedural audio
|
|
90
|
+
|
|
91
|
+
`ProceduralAudio` is a static class generating every sound from math into
|
|
92
|
+
cached AudioStreamWAVs - zero audio files; swap in recordings later
|
|
93
|
+
without touching call sites. Catalog: footsteps (4 variations),
|
|
94
|
+
door open/close/locked, ambient drone (detuned 40Hz sines, looping),
|
|
95
|
+
tension hit (sub boom + metallic ring), pickup chime, creature
|
|
96
|
+
whisper/growl/screech, heartbeat, distant scream, water drip, machinery
|
|
97
|
+
hum, wind vent, fluorescent buzz, pipe stress, radio static.
|
|
98
|
+
|
|
99
|
+
Generation pattern all sounds follow (16-bit, 22050 Hz):
|
|
100
|
+
|
|
101
|
+
```gdscript
|
|
102
|
+
static func _generate_example() -> void:
|
|
103
|
+
var num_samples := int(duration * SAMPLE_RATE)
|
|
104
|
+
var samples := PackedByteArray()
|
|
105
|
+
samples.resize(num_samples * 2)
|
|
106
|
+
for s in num_samples:
|
|
107
|
+
var t := float(s) / SAMPLE_RATE
|
|
108
|
+
var envelope := exp(-t * 3.0)
|
|
109
|
+
var value := sin(t * 440.0 * TAU) * envelope * 0.3
|
|
110
|
+
var sample := clampi(int(value * 16000), -32768, 32767)
|
|
111
|
+
samples[s * 2] = sample & 0xFF
|
|
112
|
+
samples[s * 2 + 1] = (sample >> 8) & 0xFF
|
|
113
|
+
_stream = AudioStreamWAV.new()
|
|
114
|
+
_stream.format = AudioStreamWAV.FORMAT_16_BITS
|
|
115
|
+
_stream.mix_rate = SAMPLE_RATE
|
|
116
|
+
_stream.data = samples
|
|
117
|
+
# looping sounds: loop_mode = LOOP_FORWARD, loop_begin = 0, loop_end = num_samples
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Recipes that matter: creature sounds = low fundamentals (30Hz growl) +
|
|
121
|
+
inharmonic partials; whispers/screams = filtered noise; drones = slightly
|
|
122
|
+
detuned sine pairs (beating); always exponential envelopes.
|
|
123
|
+
|
|
124
|
+
## Interactables
|
|
125
|
+
|
|
126
|
+
Same duck-typed interface as 3d-fps: `interact(player)` +
|
|
127
|
+
`get_interaction_prompt() -> String`, found by the InteractionRay (mask 4).
|
|
128
|
+
|
|
129
|
+
### HorrorDoor
|
|
130
|
+
|
|
131
|
+
Node3D rotating on Y, hinge at the parent origin (mesh offset
|
|
132
|
+
(0.5, 1.4, 0)), lerp `rotation.y` toward target at open_speed. Opens away
|
|
133
|
+
from the player (approach-side check). Horror-specific exports:
|
|
134
|
+
|
|
135
|
+
- `required_key`: locked until `GameManager.has_item(key)`; unlocking
|
|
136
|
+
consumes the key. Locked interaction plays a rattle - +-2 degree shake
|
|
137
|
+
tween + locked sound. Prompt: "Locked" / "Unlock" / "Open" / "Close".
|
|
138
|
+
- `auto_close_delay`: door closes itself behind the player (room traps).
|
|
139
|
+
- `creepy_auto_open`: opens by itself ONCE after a delay - cheapest scare
|
|
140
|
+
in the kit.
|
|
141
|
+
|
|
142
|
+
### InteractableItem
|
|
143
|
+
|
|
144
|
+
RigidBody3D with `freeze = true`, bob + rotate idle (randomized phase),
|
|
145
|
+
per-type runtime mesh + OmniLight3D glow:
|
|
146
|
+
|
|
147
|
+
| Type | Effect | Visual |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| KEY | `add_item(item_id)` | gold box, warm glow |
|
|
150
|
+
| FLASHLIGHT | `player.pickup_flashlight()` | gray cylinder, amber glow |
|
|
151
|
+
| NOTE | `open_note(id, text)` - NOT consumed | paper-white flat box |
|
|
152
|
+
| BATTERY | battery +30 (cap 100) | dark blue box, blue glow |
|
|
153
|
+
|
|
154
|
+
Everything except notes `queue_free()`s on pickup.
|
|
155
|
+
|
|
156
|
+
### EscapeZone
|
|
157
|
+
|
|
158
|
+
Area3D (mask 1) at the exit with a green OmniLight beacon. On player
|
|
159
|
+
enter: with the exit key -> `set_state(ESCAPED)`; without it -> open a
|
|
160
|
+
note ("The door is chained from the outside. I need a key.") - the
|
|
161
|
+
rejection delivers story instead of a dead trigger.
|
|
162
|
+
|
|
163
|
+
## Objective chain
|
|
164
|
+
|
|
165
|
+
The loop is explore -> find -> unlock -> escape:
|
|
166
|
+
|
|
167
|
+
1. Spawn in entry area, find flashlight.
|
|
168
|
+
2. Main hall, Note 1 hints at the storage key.
|
|
169
|
+
3. Side corridor -> office -> storage key.
|
|
170
|
+
4. Storage room (locked) -> exit key.
|
|
171
|
+
5. Back corridor (darkest) -> escape zone with exit key -> ESCAPED.
|
|
172
|
+
|
|
173
|
+
The whole chain is data: `required_key` on doors, `item_id` on pickups,
|
|
174
|
+
`required_key` on the EscapeZone, and note texts for direction. Re-theme a
|
|
175
|
+
level by editing strings, not code. Keys live in dead ends so the stalker
|
|
176
|
+
gets backtracking time; notes double as the hint system - no quest UI.
|
|
177
|
+
|
|
178
|
+
## Escalation timeline (why it works)
|
|
179
|
+
|
|
180
|
+
Minute 1: quiet, base ambient sounds, flashlight secured. Minute 2-3:
|
|
181
|
+
first stalker appearance, whispers join the pool. Mid-game: battery
|
|
182
|
+
pressure, screams, shorter stalker gaps, brief-darkness events. Endgame:
|
|
183
|
+
tension near 1.0 - vignette closed in, breathing sway, growls, stalker
|
|
184
|
+
every 8s, in the darkest corridor with the exit key. All of it falls out
|
|
185
|
+
of one passive float plus event spikes; tune the drain rate, the
|
|
186
|
+
reappear floor, and ambient intervals - not scripted scares.
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# 3D Horror - Core
|
|
2
|
+
|
|
3
|
+
First-person horror blueprint: game state, the vulnerable-player controller,
|
|
4
|
+
flashlight mechanics, level building, lighting, and the minimal HUD. The
|
|
5
|
+
stalker AI, tension/atmosphere system, procedural audio, and interactables
|
|
6
|
+
live in `3d-horror-systems.md`.
|
|
7
|
+
|
|
8
|
+
The player scene reuses the 3d-fps hierarchy and controller (yaw on root,
|
|
9
|
+
pitch on Head, two crouch shapes, headbob) - see `3d-fps.md`. This file
|
|
10
|
+
covers what horror changes.
|
|
11
|
+
|
|
12
|
+
## Design law: vulnerability
|
|
13
|
+
|
|
14
|
+
No weapon, ever. The flashlight is the only tool. The moment the player can
|
|
15
|
+
fight back it becomes an action game. No health bar, no minimap, no kill
|
|
16
|
+
counter - the HUD shows only battery, interaction prompt, and notes.
|
|
17
|
+
|
|
18
|
+
## Minimum playable base (build this first)
|
|
19
|
+
|
|
20
|
+
One dark room + flashlight pickup + a locked door + the key that opens it.
|
|
21
|
+
Prove "dark, need the light, find key, open door" before any AI or
|
|
22
|
+
atmosphere exists.
|
|
23
|
+
|
|
24
|
+
## Autoloads
|
|
25
|
+
|
|
26
|
+
Two: GameManager and AudioManager, both `process_mode = PROCESS_MODE_ALWAYS`.
|
|
27
|
+
|
|
28
|
+
GameManager is the single source of truth:
|
|
29
|
+
|
|
30
|
+
```gdscript
|
|
31
|
+
enum GameState { MENU, PLAYING, PAUSED, GAME_OVER, ESCAPED }
|
|
32
|
+
|
|
33
|
+
var inventory: Array[String] = []
|
|
34
|
+
var notes_found: Array[String] = []
|
|
35
|
+
var has_flashlight: bool = false
|
|
36
|
+
var flashlight_battery: float = 100.0
|
|
37
|
+
var doors_unlocked: Array[String] = []
|
|
38
|
+
var tension_level: float = 0.0 # 0 calm .. 1 max
|
|
39
|
+
var is_reading_note: bool = false
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- `set_state()` pauses the tree for PAUSED/GAME_OVER/ESCAPED, unpauses on
|
|
43
|
+
PLAYING, and force-closes any open note on leaving PLAYING.
|
|
44
|
+
- Tension rises passively 0.005/sec while PLAYING; systems spike it via
|
|
45
|
+
`increase_tension(amount)`. Everything atmospheric reads this one float.
|
|
46
|
+
- Note reading is a modal state: while `is_reading_note`, the player
|
|
47
|
+
controller AND the stalker both stand down (safe reading).
|
|
48
|
+
- Inventory is just string ids: `add_item` / `has_item` / `remove_item`;
|
|
49
|
+
`open_note(id, text)` / `close_note()` emit signals the HUD listens to.
|
|
50
|
+
|
|
51
|
+
AudioManager: 8 pooled AudioStreamPlayers on an "SFX" bus, dedicated
|
|
52
|
+
ambient + music players on an "Ambient" bus, buses created at runtime via
|
|
53
|
+
`AudioServer.add_bus()` if missing. `play_ambient` crossfades from -40dB.
|
|
54
|
+
Bus separation is mandatory - you cannot balance or add reverb/low-pass
|
|
55
|
+
later without it.
|
|
56
|
+
|
|
57
|
+
## Player controller (horror deltas from 3d-fps)
|
|
58
|
+
|
|
59
|
+
- Walk 3.0 / sprint 5.0 / crouch 1.5 (slow = vulnerable; FPS-speed movement
|
|
60
|
+
kills dread).
|
|
61
|
+
- gravity_multiplier 1.5 - heavier, grounded.
|
|
62
|
+
- ALL input gated on `GameManager.current_state == PLAYING` in both
|
|
63
|
+
`_unhandled_input` and `_physics_process` - frozen during notes, pause,
|
|
64
|
+
game over.
|
|
65
|
+
- Extra child under Camera3D: Flashlight (SpotLight3D at (0.15, -0.1, -0.1),
|
|
66
|
+
hidden until picked up) + FlashlightDust (GPUParticles3D).
|
|
67
|
+
- Input map adds `flashlight` (F); no shoot/aim/reload.
|
|
68
|
+
- Footsteps route through the procedural audio footstep with random pitch.
|
|
69
|
+
|
|
70
|
+
### Flashlight (the core mechanic)
|
|
71
|
+
|
|
72
|
+
```gdscript
|
|
73
|
+
func _handle_flashlight_drain(delta: float) -> void:
|
|
74
|
+
if not flashlight_on:
|
|
75
|
+
return
|
|
76
|
+
GameManager.flashlight_battery -= flashlight_drain_rate * delta / 60.0 # 15%/min, ~6.5 min life
|
|
77
|
+
GameManager.flashlight_battery = maxf(GameManager.flashlight_battery, 0.0)
|
|
78
|
+
if GameManager.flashlight_battery < 15.0:
|
|
79
|
+
# dual-sine = irregular organic flicker, not a metronome
|
|
80
|
+
var flicker := sin(Time.get_ticks_msec() * 0.03) * sin(Time.get_ticks_msec() * 0.07)
|
|
81
|
+
flashlight.light_energy = lerpf(0.3, 1.2, (flicker + 1.0) * 0.5)
|
|
82
|
+
else:
|
|
83
|
+
flashlight.light_energy = 1.5
|
|
84
|
+
if GameManager.flashlight_battery <= 0.0:
|
|
85
|
+
flashlight_on = false
|
|
86
|
+
flashlight.visible = false
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Toggle refuses until `GameManager.has_flashlight`. Battery pickups restore
|
|
90
|
+
+30. Drain rate is the game's difficulty dial.
|
|
91
|
+
|
|
92
|
+
Dust in the beam: GPUParticles3D (60 particles, 4s life, box emission
|
|
93
|
+
2x1.5x5 in front of camera, tiny unshaded spheres radius 0.008, alpha ramp
|
|
94
|
+
peaking at 0.08). Emits only while the flashlight is visible. Reads as
|
|
95
|
+
volumetrics at a fraction of the cost.
|
|
96
|
+
|
|
97
|
+
### Breathing (fear you can feel)
|
|
98
|
+
|
|
99
|
+
```gdscript
|
|
100
|
+
func _handle_breathing(delta: float) -> void:
|
|
101
|
+
var target := 0.0
|
|
102
|
+
if stamina < 30.0:
|
|
103
|
+
target += (1.0 - stamina / 30.0) * 0.5
|
|
104
|
+
target += GameManager.tension_level * 0.3
|
|
105
|
+
breathing_intensity = lerpf(breathing_intensity, target, 2.0 * delta)
|
|
106
|
+
if breathing_intensity > 0.05:
|
|
107
|
+
var t := Time.get_ticks_msec() * 0.001
|
|
108
|
+
camera.rotation.x += sin(t * 1.5) * breathing_intensity * 0.003
|
|
109
|
+
camera.rotation.z = lerpf(camera.rotation.z,
|
|
110
|
+
sin(t * 2.0) * breathing_intensity * 0.002, 3.0 * delta)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Involuntary camera sway from low stamina + high tension. Subtle numbers -
|
|
114
|
+
resist cranking them.
|
|
115
|
+
|
|
116
|
+
## Level building (library first, primitives last)
|
|
117
|
+
|
|
118
|
+
The level comes from real assets: route it through the `asset-strategy` skill
|
|
119
|
+
(`summer_search_assets`, then `summer_import_asset` / `summer_import_asset_by_id`)
|
|
120
|
+
and look for a ready horror location (an abandoned house, a haunted forest, a
|
|
121
|
+
modular horror-house kit) plus period props, keeping one coherent style. Import
|
|
122
|
+
the location that fits and build gameplay inside it. Do NOT grey-box a level a
|
|
123
|
+
real asset already provides.
|
|
124
|
+
|
|
125
|
+
Runtime primitive geometry (BoxMesh/PlaneMesh + StaticBody3D, collision
|
|
126
|
+
layer 2) is only for what the imported assets lack: interior partitions the
|
|
127
|
+
imported location does not have, trigger volumes, blockers. When you do need
|
|
128
|
+
it: WALL_HEIGHT 3.5, WALL_THICKNESS 0.15, helpers `_floor(center, size)`,
|
|
129
|
+
`_ceiling`, `_wall_x(z, x0, x1)` / `_wall_z(x, z0, z1)`, `_prop_box(pos,
|
|
130
|
+
size, mat)`. Materials: dark desaturated albedo (floor 0.12/0.11/0.1, walls
|
|
131
|
+
0.18/0.17/0.15), roughness 0.85.
|
|
132
|
+
|
|
133
|
+
Layout formula:
|
|
134
|
+
|
|
135
|
+
1. Entry area - well-lit, flashlight pickup here.
|
|
136
|
+
2. Main hall - central hub, multiple exits, player keeps returning.
|
|
137
|
+
3. Side corridors - long, narrow, dim; stalker territory.
|
|
138
|
+
4. Dead-end rooms - keys and notes; forced backtracking.
|
|
139
|
+
5. Back corridor - darkest, highest tension.
|
|
140
|
+
6. Final area - escape zone at the far end, exit key behind at least one
|
|
141
|
+
locked door.
|
|
142
|
+
|
|
143
|
+
## Atmosphere rendering settings
|
|
144
|
+
|
|
145
|
+
WorldEnvironment is half the horror:
|
|
146
|
+
|
|
147
|
+
- Sky near-black (top 0.02/0.02/0.05); ambient color 0.03/0.03/0.05 at
|
|
148
|
+
energy 0.5 - the player MUST need the flashlight.
|
|
149
|
+
- Tonemap ACES, white 6.0. SSAO on (radius 2, intensity 3 - darkens
|
|
150
|
+
corners). SSIL on (radius 3, intensity 0.5). Glow subtle (0.3 / bloom 0.1).
|
|
151
|
+
- Fog density 0.02 dark; volumetric fog density 0.06, anisotropy 0.7 -
|
|
152
|
+
this is what makes the flashlight beam a visible cone.
|
|
153
|
+
|
|
154
|
+
Lighting rules: OmniLights energy 0.4-0.8 (main hall 1.2 max), ALL with
|
|
155
|
+
`shadow_enabled = true` (the stalker casts a shadow before you see it).
|
|
156
|
+
Give each area a distinct temperature - entry warm (0.6, 0.5, 0.3),
|
|
157
|
+
corridors cool (0.3, 0.4, 0.35), lab sickly green (0.3, 0.6, 0.4) - it is
|
|
158
|
+
navigation AND variety without textures.
|
|
159
|
+
|
|
160
|
+
Collision layers: 1 Player, 2 Environment, 3 Interactable, 4 Enemy.
|
|
161
|
+
|
|
162
|
+
## HUD (minimal by design)
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
HUD (CanvasLayer)
|
|
166
|
+
Crosshair (centered dot)
|
|
167
|
+
InteractionPrompt (Label, bottom center, "[E] Open")
|
|
168
|
+
BatteryContainer (top left, HIDDEN until flashlight found)
|
|
169
|
+
BatteryBar (ProgressBar; red <20%, yellow <50%, blue else)
|
|
170
|
+
NoteBackdrop (fullscreen black, 72% alpha)
|
|
171
|
+
NotePanel (centered note text)
|
|
172
|
+
Vignette (fullscreen; alpha = 0.2 + tension * 0.4)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Wire to GameManager signals (`note_opened/closed`, `game_state_changed`).
|
|
176
|
+
Note UX detail: 0.35s cooldown after opening before close input is
|
|
177
|
+
accepted - otherwise the same interact press that opened the note closes
|
|
178
|
+
it. HUD hides entirely when state is not PLAYING.
|
|
179
|
+
|
|
180
|
+
## Build order
|
|
181
|
+
|
|
182
|
+
1. Autoloads + input map + layers.
|
|
183
|
+
2. Player from 3d-fps base, horror speeds, state gating.
|
|
184
|
+
3. One room + WorldEnvironment settings + flashlight pickup + drain.
|
|
185
|
+
4. Doors/keys/notes + escape zone (`3d-horror-systems.md`).
|
|
186
|
+
5. Atmosphere controller, then the stalker LAST - it only lands once the
|
|
187
|
+
world already feels wrong.
|
|
188
|
+
|
|
189
|
+
## Traps
|
|
190
|
+
|
|
191
|
+
- Giving the player any weapon, health bar, or combat UI.
|
|
192
|
+
- Ambient energy above 0.5 - no darkness, no battery economy, no fear.
|
|
193
|
+
- Constant jump scares. The template leans on anticipation; one of its
|
|
194
|
+
five tension events is deliberately NOTHING.
|
|
195
|
+
- Stalker always visible - players habituate; long gaps are intentional.
|
|
196
|
+
- Constant music. Silence scares; use the drone + rare stingers, save
|
|
197
|
+
music for peaks and safe rooms.
|
|
198
|
+
- Identical rooms - distinct color temperature per area or players get
|
|
199
|
+
lost and bored simultaneously.
|
|
200
|
+
- Skipping audio buses - SFX through SFX bus, ambience through Ambient,
|
|
201
|
+
or mixing and wall-muffling effects become impossible later.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# 3D Third-Person - Systems
|
|
2
|
+
|
|
3
|
+
The combat and content layer on top of `3d-third-person.md`: melee, bullets,
|
|
4
|
+
grenade with arc preview, two enemy archetypes, coins, destructible boxes,
|
|
5
|
+
and the HUD.
|
|
6
|
+
|
|
7
|
+
## Shared damage interface
|
|
8
|
+
|
|
9
|
+
Everything hittable (player, enemies, boxes) implements:
|
|
10
|
+
|
|
11
|
+
```gdscript
|
|
12
|
+
func damage(impact_point: Vector3, force: Vector3) -> void
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
and joins the "damageables" group. `impact_point` is
|
|
16
|
+
`global_position - body.global_position` (direction from body to impact),
|
|
17
|
+
`force` is the knockback vector. Enemies additionally join "targeteables"
|
|
18
|
+
for grenade lock-on. One interface, three weapons, all content - keep it.
|
|
19
|
+
|
|
20
|
+
## Weapons
|
|
21
|
+
|
|
22
|
+
Tab toggles DEFAULT and GRENADE (a `weapon_switched` signal drives the HUD
|
|
23
|
+
icons). DEFAULT: left-click = melee from the hip, aim (RMB) + left-click =
|
|
24
|
+
ranged shot with over-shoulder camera + crosshair. GRENADE: left-click
|
|
25
|
+
throws with a live arc preview.
|
|
26
|
+
|
|
27
|
+
### Melee (animation-gated hitbox)
|
|
28
|
+
|
|
29
|
+
The MeleeAttackArea's CollisionShape3D starts disabled. The "Attack"
|
|
30
|
+
animation (0.3-0.5s) has method-call tracks: `activate()` at the impact
|
|
31
|
+
frame (~0.15s), `deactivate()` at the end. The area's `body_entered` damages
|
|
32
|
+
"damageables" with force pointing away from the player. Player side:
|
|
33
|
+
`attack()` plays the animation, fires the skin's punch OneShot, and sets
|
|
34
|
+
`velocity = rotation_root.basis * Vector3.BACK * attack_impulse` (forward
|
|
35
|
+
lunge). Movement is locked while the attack animation plays.
|
|
36
|
+
|
|
37
|
+
Animation-gated hitboxes beat timer-gated ones: the active window is
|
|
38
|
+
authored visually next to the swing frames.
|
|
39
|
+
|
|
40
|
+
### Bullet (shared by player AND enemies)
|
|
41
|
+
|
|
42
|
+
Node3D moved manually in `_process` - no physics body:
|
|
43
|
+
|
|
44
|
+
```gdscript
|
|
45
|
+
func _ready() -> void:
|
|
46
|
+
_area.body_entered.connect(_on_body_entered)
|
|
47
|
+
look_at(global_position + velocity)
|
|
48
|
+
_alive_limit = distance_limit / velocity.length()
|
|
49
|
+
|
|
50
|
+
func _process(delta: float) -> void:
|
|
51
|
+
global_position += velocity * delta
|
|
52
|
+
_time_alive += delta
|
|
53
|
+
_bullet_visuals.scale = Vector3.ONE * scale_decay.sample(_time_alive / _alive_limit)
|
|
54
|
+
if _time_alive > _alive_limit:
|
|
55
|
+
queue_free()
|
|
56
|
+
|
|
57
|
+
func _on_body_entered(body: Node3D) -> void:
|
|
58
|
+
if body == shooter:
|
|
59
|
+
return
|
|
60
|
+
if body.is_in_group("damageables"):
|
|
61
|
+
body.damage(global_position - body.global_position, velocity)
|
|
62
|
+
queue_free()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The `shooter` reference prevents self-hits, which is what lets one bullet
|
|
66
|
+
scene serve both sides. Distance limit expressed as time
|
|
67
|
+
(`distance / speed`); visual scale decays via an exported Curve so bullets
|
|
68
|
+
fade with range. Player shots originate at `global_position + Vector3.UP`
|
|
69
|
+
and fly toward `camera_controller.get_aim_target()`.
|
|
70
|
+
|
|
71
|
+
Fast projectiles tunnel through thin walls: keep speeds moderate (player
|
|
72
|
+
10, enemies 6) or fatten the Area3D shape.
|
|
73
|
+
|
|
74
|
+
### Grenade launcher (arc preview + lock-on)
|
|
75
|
+
|
|
76
|
+
While GRENADE is equipped the launcher is visible and every physics frame:
|
|
77
|
+
camera tilt lerps throw distance between min 7 and max 16; a ShapeCast3D
|
|
78
|
+
finds the landing point; if the cast hits a "targeteables" body, the landing
|
|
79
|
+
snaps to that enemy (lock-on, SnapMesh indicator); initial velocity
|
|
80
|
+
decomposes into vertical `sqrt(2 * gravity * peak_height)` and horizontal
|
|
81
|
+
`distance / time_to_land`; the preview trail is a triangle-strip mesh built
|
|
82
|
+
with SurfaceTool sampling the parabola at 0.05s steps.
|
|
83
|
+
|
|
84
|
+
The grenade itself is a CharacterBody3D using `move_and_collide` (NOT
|
|
85
|
+
move_and_slide) so it can bounce:
|
|
86
|
+
|
|
87
|
+
```gdscript
|
|
88
|
+
func _physics_process(delta) -> void:
|
|
89
|
+
_velocity += Vector3.DOWN * gravity * delta
|
|
90
|
+
var collision := move_and_collide(_velocity * delta)
|
|
91
|
+
if collision:
|
|
92
|
+
_velocity = _velocity.bounce(collision.get_normal(0)) * 0.7
|
|
93
|
+
if _explosion_start_timer.is_stopped():
|
|
94
|
+
_explosion_start_timer.start()
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
First bounce arms the fuse timer. Explosion: damage all overlapping
|
|
98
|
+
"damageables" in the ExplosionArea EXCEPT the Player (`not body is Player` -
|
|
99
|
+
self-immunity), spawn explosion visuals, hide, await the sound, free. On
|
|
100
|
+
throw, add a collision exception between grenade and thrower via
|
|
101
|
+
`PhysicsServer3D.body_add_collision_exception`.
|
|
102
|
+
|
|
103
|
+
## Enemies
|
|
104
|
+
|
|
105
|
+
Both are RigidBody3D with a PlayerDetectionArea (big sphere Area3D), a
|
|
106
|
+
reaction label ("!" / "?" animations on detect/lose), and the same death
|
|
107
|
+
choreography: play defeat sound, set `_alive = false`, DISCONNECT the
|
|
108
|
+
detection signals (dead enemies must not react), enable the death collider,
|
|
109
|
+
enable gravity so the body drops, wait 2s, spawn a smoke puff, `await
|
|
110
|
+
puff.full`, burst coins out of the smoke, `queue_free()`.
|
|
111
|
+
|
|
112
|
+
The smoke puff emits `full` from an animation keyframe at peak opacity -
|
|
113
|
+
awaiting it makes coins appear to burst out of the cloud instead of
|
|
114
|
+
clipping through a half-formed puff.
|
|
115
|
+
|
|
116
|
+
### Bee bot (flying turret)
|
|
117
|
+
|
|
118
|
+
RigidBody3D with `gravity_scale = 0` (floats). While a target is set:
|
|
119
|
+
|
|
120
|
+
```gdscript
|
|
121
|
+
var target_transform := transform.looking_at(_target.global_position)
|
|
122
|
+
transform = transform.interpolate_with(target_transform, 0.1)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Fires the SAME bullet scene as the player every 1.5s at speed 6, aimed at
|
|
126
|
+
`target.global_position + Vector3.UP`. `damage()` applies an impulse
|
|
127
|
+
(clamped length 3) then runs the death choreography; gravity_scale flips to
|
|
128
|
+
1.0 so it falls out of the air.
|
|
129
|
+
|
|
130
|
+
### Beetle bot (NavigationAgent3D chaser)
|
|
131
|
+
|
|
132
|
+
RigidBody3D with rotation axes locked (rigid until death). Chase loop:
|
|
133
|
+
|
|
134
|
+
```gdscript
|
|
135
|
+
_navigation_agent.target_position = _target.global_position
|
|
136
|
+
var next := _navigation_agent.get_next_path_position()
|
|
137
|
+
if not _navigation_agent.is_target_reached():
|
|
138
|
+
var direction := (next - global_position)
|
|
139
|
+
direction.y = 0 # never dive/fly
|
|
140
|
+
direction = direction.normalized()
|
|
141
|
+
var collision := move_and_collide(direction * delta * 3)
|
|
142
|
+
if collision and collision.get_collider() is Player:
|
|
143
|
+
var force := -(global_position - collider.global_position)
|
|
144
|
+
force.y = 0.5
|
|
145
|
+
collider.damage(impact_point, force * 10.0)
|
|
146
|
+
_beetle_skin.attack()
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`look_at` the target with Y flattened to its own height. On death, unlock
|
|
150
|
+
all angular axes so the corpse tumbles.
|
|
151
|
+
|
|
152
|
+
Navigation requirements: the level MUST have a baked NavigationRegion3D or
|
|
153
|
+
the beetle stands still forever; the target must be reachable on the
|
|
154
|
+
navmesh; always zero the Y of path directions.
|
|
155
|
+
|
|
156
|
+
Enemy skins: AnimationTree state machines (Idle/Walk/Attack/Shake/PowerOff)
|
|
157
|
+
with a secondary-action timer firing random idle shakes every 3-8s;
|
|
158
|
+
`power_off()` stops that timer.
|
|
159
|
+
|
|
160
|
+
## Coins
|
|
161
|
+
|
|
162
|
+
RigidBody3D collectible, either placed in the level or scattered by
|
|
163
|
+
`spawn()` (random impulse: direction rotated randomly around UP, range 2-4,
|
|
164
|
+
height 1-3). Collection is DELAYED: layer 3 collision is off for
|
|
165
|
+
`coin_delay` (0.5s default, 1.5s when the player drops them) so coins from
|
|
166
|
+
a kill do not vacuum back instantly.
|
|
167
|
+
|
|
168
|
+
Magnet: when the player enters the coin's detection area (mask = Player
|
|
169
|
+
layer 4), freeze physics and tween to the player:
|
|
170
|
+
|
|
171
|
+
```gdscript
|
|
172
|
+
func set_target(new_target: PhysicsBody3D) -> void:
|
|
173
|
+
PhysicsServer3D.body_add_collision_exception(get_rid(), new_target.get_rid())
|
|
174
|
+
if _target == null:
|
|
175
|
+
sleeping = true
|
|
176
|
+
freeze = true
|
|
177
|
+
_initial_tween_position = global_position
|
|
178
|
+
_target = new_target
|
|
179
|
+
var tween := create_tween()
|
|
180
|
+
tween.tween_method(_follow, 0.0, 1.0, 0.5) # lerp start -> player
|
|
181
|
+
tween.tween_callback(_collect)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`_follow` lerps from the captured start to the LIVE player position, so the
|
|
185
|
+
coin homes even while the player runs. Collect: random-pitch sound,
|
|
186
|
+
`player.collect_coin()`, hide, await audio, free. Coin model: spin
|
|
187
|
+
`rotation.y += 1.5 * delta`, bob `sin(t) * 0.04`.
|
|
188
|
+
|
|
189
|
+
## Destructible boxes
|
|
190
|
+
|
|
191
|
+
`damage()` on the box: spawn 5 coins with `spawn()`, instantiate a
|
|
192
|
+
pre-fractured debris scene, disable own collision (deferred), play sound,
|
|
193
|
+
await it, free. Debris scene = 6 hidden frozen RigidBody3D pieces; on ready,
|
|
194
|
+
shuffle and launch 3 random ones with `apply_force(rand_vector * 500)`,
|
|
195
|
+
leave the rest hidden. Half-count random debris looks as good as full
|
|
196
|
+
simulation at a fraction of the cost.
|
|
197
|
+
|
|
198
|
+
## HUD
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
UI (CanvasLayer)
|
|
202
|
+
AimRecticle (%unique, ColorRect centered) # visible only while aiming
|
|
203
|
+
CoinsContainer (%unique, HBoxContainer) # slides in on change
|
|
204
|
+
CoinIcon + CoinsLabel + Timer
|
|
205
|
+
WeaponUI (PanelContainer) # Flash / Grenade icons
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Coin counter: slides from y -100 to y 20 on `update_coins_amount`, restarts
|
|
209
|
+
its display timer per change, slides back out on timeout. Weapon icons:
|
|
210
|
+
inactive at alpha 0.2, tween to full white on select; `switch_to()` guards
|
|
211
|
+
against reselecting the current one. Use unique names (%) so the deep
|
|
212
|
+
player script reaches UI without brittle paths.
|
|
213
|
+
|
|
214
|
+
## Order of work
|
|
215
|
+
|
|
216
|
+
1. Melee: attack animation + gated hitbox + a destructible box to punch.
|
|
217
|
+
2. Coins with magnet + HUD counter (the reward loop).
|
|
218
|
+
3. Aim mode: over-shoulder pivot + crosshair + bullet.
|
|
219
|
+
4. Bee bot (reuses your bullet), then beetle bot (navmesh required).
|
|
220
|
+
5. Grenade launcher last - arc preview, lock-on, explosion.
|