Part of the
make-game skill. The skill links here when your agent needs the detail.3d-horror-systems.md.
The player scene reuses the 3d-fps hierarchy and controller (yaw on root,
pitch on Head, two crouch shapes, headbob) - see 3d-fps.md. This file
covers what horror changes.
Design law: vulnerability
No weapon, ever. The flashlight is the only tool. The moment the player can fight back it becomes an action game. No health bar, no minimap, no kill counter - the HUD shows only battery, interaction prompt, and notes.Minimum playable base (build this first)
One dark room + flashlight pickup + a locked door + the key that opens it. Prove “dark, need the light, find key, open door” before any AI or atmosphere exists.Autoloads
Two: GameManager and AudioManager, bothprocess_mode = PROCESS_MODE_ALWAYS.
GameManager is the single source of truth:
set_state()pauses the tree for PAUSED/GAME_OVER/ESCAPED, unpauses on PLAYING, and force-closes any open note on leaving PLAYING.- Tension rises passively 0.005/sec while PLAYING; systems spike it via
increase_tension(amount). Everything atmospheric reads this one float. - Note reading is a modal state: while
is_reading_note, the player controller AND the stalker both stand down (safe reading). - Inventory is just string ids:
add_item/has_item/remove_item;open_note(id, text)/close_note()emit signals the HUD listens to.
AudioServer.add_bus() if missing. play_ambient crossfades from -40dB.
Bus separation is mandatory - you cannot balance or add reverb/low-pass
later without it.
Player controller (horror deltas from 3d-fps)
- Walk 3.0 / sprint 5.0 / crouch 1.5 (slow = vulnerable; FPS-speed movement kills dread).
- gravity_multiplier 1.5 - heavier, grounded.
- ALL input gated on
GameManager.current_state == PLAYINGin both_unhandled_inputand_physics_process- frozen during notes, pause, game over. - Extra child under Camera3D: Flashlight (SpotLight3D at (0.15, -0.1, -0.1), hidden until picked up) + FlashlightDust (GPUParticles3D).
- Input map adds
flashlight(F); no shoot/aim/reload. - Footsteps route through the procedural audio footstep with random pitch.
Flashlight (the core mechanic)
GameManager.has_flashlight. Battery pickups restore
+30. Drain rate is the game’s difficulty dial.
Dust in the beam: GPUParticles3D (60 particles, 4s life, box emission
2x1.5x5 in front of camera, tiny unshaded spheres radius 0.008, alpha ramp
peaking at 0.08). Emits only while the flashlight is visible. Reads as
volumetrics at a fraction of the cost.
Breathing (fear you can feel)
Level building (library first, primitives last)
The level comes from real assets: route it through theasset-strategy skill
(assets.search, then summer_import_asset / summer_import_asset_by_id)
and look for a ready horror location (an abandoned house, a haunted forest, a
modular horror-house kit) plus period props, keeping one coherent style. Import
the location that fits and build gameplay inside it. Do NOT grey-box a level a
real asset already provides.
Runtime primitive geometry (BoxMesh/PlaneMesh + StaticBody3D, collision
layer 2) is only for what the imported assets lack: interior partitions the
imported location does not have, trigger volumes, blockers. When you do need
it: WALL_HEIGHT 3.5, WALL_THICKNESS 0.15, helpers _floor(center, size),
_ceiling, _wall_x(z, x0, x1) / _wall_z(x, z0, z1), _prop_box(pos, size, mat). Materials: dark desaturated albedo (floor 0.12/0.11/0.1, walls
0.18/0.17/0.15), roughness 0.85.
Layout formula:
- Entry area - well-lit, flashlight pickup here.
- Main hall - central hub, multiple exits, player keeps returning.
- Side corridors - long, narrow, dim; stalker territory.
- Dead-end rooms - keys and notes; forced backtracking.
- Back corridor - darkest, highest tension.
- Final area - escape zone at the far end, exit key behind at least one locked door.
Atmosphere rendering settings
WorldEnvironment is half the horror:- Sky near-black (top 0.02/0.02/0.05); ambient color 0.03/0.03/0.05 at energy 0.5 - the player MUST need the flashlight.
- Tonemap ACES, white 6.0. SSAO on (radius 2, intensity 3 - darkens corners). SSIL on (radius 3, intensity 0.5). Glow subtle (0.3 / bloom 0.1).
- Fog density 0.02 dark; volumetric fog density 0.06, anisotropy 0.7 - this is what makes the flashlight beam a visible cone.
shadow_enabled = true (the stalker casts a shadow before you see it).
Give each area a distinct temperature - entry warm (0.6, 0.5, 0.3),
corridors cool (0.3, 0.4, 0.35), lab sickly green (0.3, 0.6, 0.4) - it is
navigation AND variety without textures.
Collision layers: 1 Player, 2 Environment, 3 Interactable, 4 Enemy.
HUD (minimal by design)
note_opened/closed, game_state_changed).
Note UX detail: 0.35s cooldown after opening before close input is
accepted - otherwise the same interact press that opened the note closes
it. HUD hides entirely when state is not PLAYING.
Build order
- Autoloads + input map + layers.
- Player from 3d-fps base, horror speeds, state gating.
- One room + WorldEnvironment settings + flashlight pickup + drain.
- Doors/keys/notes + escape zone (
3d-horror-systems.md). - Atmosphere controller, then the stalker LAST - it only lands once the world already feels wrong.
Traps
- Giving the player any weapon, health bar, or combat UI.
- Ambient energy above 0.5 - no darkness, no battery economy, no fear.
- Constant jump scares. The template leans on anticipation; one of its five tension events is deliberately NOTHING.
- Stalker always visible - players habituate; long gaps are intentional.
- Constant music. Silence scares; use the drone + rare stingers, save music for peaks and safe rooms.
- Identical rooms - distinct color temperature per area or players get lost and bored simultaneously.
- Skipping audio buses - SFX through SFX bus, ambience through Ambient, or mixing and wall-muffling effects become impossible later.
Source:
library/skills/make-game/references/3d-horror.md, rendered from summer-engine@4.3.0 on npm.
