
Godot Genre Horror
- 170 installs
- 454 repo stars
- Updated July 28, 2026
- thedivergentai/gd-agentic-skills
Use godot-genre-horror for development tasks
About
godot-genre-horror: A skill for development. This provides functionality for development workflows.
- godot-genre-horror
Godot Genre Horror by the numbers
- 170 all-time installs (skills.sh)
- +11 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #2,259 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-genre-horrorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 170 |
|---|---|
| repo stars | ★ 454 |
| Last updated | July 28, 2026 |
| Repository | thedivergentai/gd-agentic-skills ↗ |
What it does
Use godot-genre-horror for development tasks
Files
Genre: Horror
Expert blueprint for horror games balancing tension, atmosphere, and player agency.
NEVER Do (Expert Anti-Patterns)
Atmosphere & Tension
- NEVER maintain 100% tension at all times; strictly use a Sawtooth Pacing model (buildup → peak/scare → dedicated relief period) to prevent player "numbing" and exhaustion.
- NEVER rely on jump-scares as the primary source of horror; focus on atmosphere, spatial audio cues, and the anticipation of a threat to build genuine dread.
- NEVER make environments pitch black to the point of frustrating navigation; darkness should obscure threats (details), not the floor. Use rim lighting or a limited-battery flashlight.
- NEVER grant the player unlimited resources; survival horror relies on Scarcity. Limited battery, rare ammo, and slow animations are mandatory to force stressful decision-making.
AI & Senses
- NEVER allow AI to detect the player instantly; implement a Suspicion Meter or a 1-3s reaction window before the AI enters full aggression to avoid "unfair cheating" feel.
- NEVER use predictable AI paths; an enemy on a perfect loop is a puzzle, not a predator. Use the Director to periodically "hint" a new destination near the player.
- NEVER use Area3D overlap signals for instant, frame-perfect Line-of-Sight (LoS) checks; use nodeless raycasting via
PhysicsDirectSpaceState3D.intersect_ray()for fixed-physics sync. - NEVER calculate complex AI vision or pathfinding for monsters far outside the camera's frustum; use
VisibleOnScreenNotifier3Dto disable processing logic. - NEVER leave navigation avoidance layers unconfigured on chasing monsters; explicitly assign avoidance masks to prevent visual "stacking" in tight corridors.
Technical & Scarcity
- NEVER use the visual SceneTree (like GridContainer children) as the source of truth for inventory; strictly maintain a typed memory structure like
Dictionary[StringName, Resource]. - NEVER rely on instantiating standard Nodes to store base item stats/definitions; use custom
Resourcescripts to reduce memory overhead and allow direct Inspector editing. - NEVER forget to call
duplicate(true)on an item's Resource when adding to inventory; if items have mutable states (ammo/durability), you will overwrite the global resource otherwise. - NEVER parse massive JSON save files synchronously; strictly offload heavy parsing to the
WorkerThreadPoolto prevent auto-save freezes. - NEVER use standard strings for hot-path IDs (states, item types); strictly use
StringName(&"chasing") for pointer-speed comparisons. - NEVER evaluate exact floating-point equality (sanity == 0.0); strictly use
is_equal_approx()or threshold checks for deterministic triggers. - NEVER write screen-reading shaders expecting Godot 3
SCREEN_TEXTURE; strictly usesampler2Dwithhint_screen_texture. - NEVER instantiate detailed monster meshes or lights without culling; strictly configure
visibility_rangefor automatic HLOD efficiency. - NEVER rely on AnimationPlayer for random flickering; use
Tweenfor programmatic, clean energy manipulation. - NEVER load heavy scare scenes or 4K textures synchronously via
load(); strictly useResourceLoader.load_threaded_request()to prevent frame stalls. - NEVER scale CollisionShape3D non-uniformly; strictly adjust internal shape resource parameters (radius, height) to prevent erratic physics.
- NEVER perform synchronous, heavy file I/O in a Safe Room; strictly use `Thread` and `Mutex` to handle background saving without stalling the main game thread.
- NEVER check for hiding spot types by casting; strictly use `Object` metadata (`set_meta`) for performant, decoupled AI queries.
---
🛠 Expert Components (scripts/)
Original Expert Patterns
- predator_stalking_ai.gd - Sophisticated "Stalker" AI using dual-brain logic (Director + Senses) and player view-cone avoidance.
- director_pacing.gd - Invisible orchestrator managing the "Sawtooth" tension wave and relief periods.
Modular Components
- monster_los_check.gd - Physics-synced raycasting for high-performance visibility checks.
- flashlight_flicker.gd - Procedural light interference for atmospheric tension.
- inventory_data_storage.gd - Typed data structure for sparse resource management.
- async_scare_loader.gd - Threaded resource loading for hitch-free jump-scares.
- spatial_noise_emitter.gd - Shape-based sound sensing for sensory AI.
- item_state_duplicator.gd - Deep duplication for managing unique weapon/item states.
- fog_claus_intensifier.gd - Volumetric fog manipulation for dread buildup.
- offscreen_logic_suspender.gd - Culling logic for AI processing outside camera view.
- sanity_shader_manager.gd - Instance-uniform driven distortion effects.
- optimized_horror_state_machine.gd - High-speed predator behavior logic.
---
Core Loop
1. Explore: Player navigates a threatening environment. 2. Sense: Player hears/sees signs of danger. 3. React: Player hides, runs, or fights (disempowered combat). 4. Survive: Player reaches safety or solves a puzzle. 5. Relief: Brief moment of calm before tension builds again.
Skill Chain
| Phase | Skills | Purpose |
|---|---|---|
| 1. Atmosphere | godot-3d-lighting, godot-audio-systems | Volumetric fog, dynamic shadows, spatial audio |
| 2. AI | godot-state-machine-advanced, godot-navigation-pathfinding | Hunter AI, sensory perception |
| 3. Player | characterbody-3d | Leaning, hiding, slow movement |
| 4. Scarcity | godot-inventory-system | Limited battery, ammo, health |
| 5. Logic | game-manager | The "Director" system controlling pacing |
Architecture Overview
1. The Director System (Macro AI)
Controls the pacing of the game to prevent constant exhaustion.
# director.gd
extends Node
enum TensionState { BUILDUP, PEAK, RELIEF, QUIET }
var current_tension: float = 0.0
var player_stress_level: float = 0.0
func _process(delta: float) -> void:
match current_tension_state:
TensionState.BUILDUP:
current_tension += 0.5 * delta
if current_tension > 75.0:
trigger_event()
TensionState.RELIEF:
current_tension -= 2.0 * delta
func trigger_event() -> void:
# Hints the Monster AI to check a room NEAR the player, not ON the player
monster_ai.investigate_area(player.global_position + Vector3(randf(), 0, randf()) * 10)2. Sensory Perception (Micro AI)
The monster's actual senses.
# sensory_component.gd
extends Area3D
signal sound_heard(position: Vector3, volume: float)
signal player_spotted(position: Vector3)
func check_vision(target: Node3D) -> bool:
var space_state = get_world_3d().direct_space_state
var query = PhysicsRayQueryParameters3D.create(global_position, target.global_position)
var result = space_state.intersect_ray(query)
if result and result.collider == target:
return true
return false3. Sanity / Stress System
Distorting the world based on fear.
# sanity_manager.gd
func update_sanity(amount: float) -> void:
current_sanity = clamp(current_sanity + amount, 0.0, 100.0)
# Effect: Camera Shake
camera_shake_intensity = (100.0 - current_sanity) * 0.01
# Effect: Audio Distortion
audio_bus.get_effect(0).drive = (100.0 - current_sanity) * 0.05Key Mechanics Implementation
Pacing (The Sawtooth Wave)
Horror needs peaks and valleys. 1. Safety: Save room. 2. Unease: Strange noise, lights flicker. 3. Dread: Monster is known to be close. 4. Terror: Chase sequence / Combat. 5. Relief: Escape to Safety.
The "Dual Brain" AI
- Director (All-knowing): Cheats to keep the alien relevant (teleports it closer if far away, guides it to player's general area).
- Alien (Senses only): Honest AI. Must actually see/hear the player to attack.
3. Hiding-Spot Metadata System
Use decoupled Object metadata so AI can query state without knowing the class.
# hiding_spot.gd
func _ready():
add_to_group("hiding_spots")
set_meta("is_occupied", false)
# predator_ai.gd
func search_hiding_spots():
for spot in get_tree().get_nodes_in_group("hiding_spots"):
if spot.get_meta("is_occupied"):
investigate(spot.global_position)4. Adaptive Audio (Stress Muffling)
Dynamic low-pass filtering via AudioServer.
# audio_stress_manager.gd
func update_stress(fear_level: float):
# Enable LPF effect on Master bus (index 0) if fear is high
AudioServer.set_bus_effect_enabled(0, 0, fear_level > 0.5)
# Attenuate volume linearly
AudioServer.set_bus_volume_linear(0, 1.0 - (fear_level * 0.4))5. Safe-Room Multithreaded Save
Use Thread and Mutex to prevent frame drops during checkpoint saves.
# safe_room.gd
var _save_thread: Thread = Thread.new()
var _mutex: Mutex = Mutex.new()
func trigger_save(data: Dictionary):
_mutex.lock()
if not _save_thread.is_alive():
_save_thread.start(_do_save.bind(data.duplicate(true)))
_mutex.unlock()
func _do_save(data):
var file = FileAccess.open("user://save.dat", FileAccess.WRITE)
file.store_var(data)
file.close()Godot-Specific Tips
- Volumetric Fog: Use
WorldEnvironment->VolumetricFogfor instant atmosphere. Animatedensityfor dynamic dread. - Light Occluder 2D: For 2D horror, shadow casting is essential.
- AudioBus: Use
ReverbandLowPassFilteron the Master bus, controlled by scripts, to simulate "muffled" hearing when scared or hiding. - AnimationTree: Use blend spaces to smooth transitions between "Sneak", "Walk", and "Run" animations.
Common Pitfalls
1. Constant Tension: Player gets numb. Fix: Enforce "Relief" periods where nothing happens. 2. Frustrating AI: AI sees player instantly. Fix: Give AI a "reaction time" or "suspicion meter" before full aggro. 3. Too Dark: Player can't see anything. Fix: Darkness should obscure details, not navigation. Use rim lighting or a weak flashlight.
Reference
- Master Skill: godot-master
# async_scare_loader.gd
extends Node
# Asynchronous Jump-Scare Loading (Performance Optimization)
# Prevents the game thread from freezing right before a scare by pre-loading resources.
const SCARE_PATH := "res://scares/hallway_ghost.tscn"
func preload_jump_scare() -> void:
# Requests the resource loader to start loading in the background.
ResourceLoader.load_threaded_request(SCARE_PATH)
func execute_jump_scare() -> void:
# Retreives the pre-loaded resource without stalling the main thread.
var scare_scene := ResourceLoader.load_threaded_get(SCARE_PATH) as PackedScene
if scare_scene:
var instance = scare_scene.instantiate()
get_tree().root.add_child(instance)
# skills/genre-horror/scripts/director_pacing.gd
extends Node
## Director Pacing System (Expert Pattern)
## Manages game pacing using a "Sawtooth" tension wave.
## Prevents player exhaustion by enforcing relief periods after peaks.
class_name DirectorPacing
enum TensionState { BUILDUP, PEAK, RELIEF, QUIET }
signal tension_changed(value: float, state: TensionState)
signal event_triggered(event_name: String)
@export var buildup_rate: float = 0.5 # Tension added per second normally
@export var relief_rate: float = 2.0 # Tension removed per second in relief
@export var peak_threshold: float = 75.0
@export var relief_duration: float = 20.0 # Min seconds of claim
var current_tension: float = 0.0
var current_state: TensionState = TensionState.QUIET
var relief_timer: float = 0.0
func _process(delta: float) -> void:
match current_state:
TensionState.QUIET:
# Low level background tension
_modulate_tension(buildup_rate * 0.2 * delta)
if current_tension > 25.0:
current_state = TensionState.BUILDUP
TensionState.BUILDUP:
_modulate_tension(buildup_rate * delta)
if current_tension > peak_threshold:
_trigger_peak_event()
TensionState.PEAK:
# Tension stays high until player resolves threat or escapes
# Handled by external events calling 'enter_relief()'
pass
TensionState.RELIEF:
_modulate_tension(-relief_rate * delta)
relief_timer -= delta
if relief_timer <= 0 and current_tension < 10.0:
current_state = TensionState.QUIET
func _modulate_tension(amount: float) -> void:
current_tension = clamp(current_tension + amount, 0.0, 100.0)
tension_changed.emit(current_tension, current_state)
func _trigger_peak_event() -> void:
current_state = TensionState.PEAK
event_triggered.emit("monster_spawn")
# In a real game, this would query a database of available events
func enter_relief() -> void:
current_state = TensionState.RELIEF
relief_timer = relief_duration
event_triggered.emit("music_calm")
func add_stress(amount: float) -> void:
# Called by game events (e.g. seeing a corpse)
current_tension += amount
if current_state != TensionState.RELIEF:
if current_tension > peak_threshold and current_state != TensionState.PEAK:
_trigger_peak_event()
## EXPERT USAGE:
## Autoload this script. Connect to music/lighting systems.
## Call add_stress() from gameplay triggers.
# flashlight_flicker.gd
extends Node
# Procedural Flashlight Flicker (Light/Dark Atmosphere)
# Uses Tween for lightweight parameter manipulation instead of bulky AnimationPlayers.
var _flicker_tween: Tween
func trigger_flashlight_flicker(light: SpotLight3D) -> void:
# Kill existing tween to avoid stacking flickers.
if _flicker_tween:
_flicker_tween.kill()
_flicker_tween = create_tween().set_loops(4)
# Smoothly animates the light energy property to simulate failing batteries or interference.
# Pattern: Buildup of darkness -> Rapid flash back to full.
_flicker_tween.tween_property(light, "light_energy", 0.0, 0.05)
_flicker_tween.tween_property(light, "light_energy", 2.5, 0.1)
# fog_claus_intensifier.gd
extends Node
# Dynamically Adjusting Volumetric Fog (Dread Atmosphere)
# Increases the claustrophobia effect by manipulating environment density in real-time.
func intensify_fog(env: Environment, target_density: float = 0.15) -> void:
# Set this to the lowest base density you want globally.
var current_density := env.volumetric_fog_density
var tween := create_tween().set_trans(Tween.TRANS_SINE)
# Smoothly increase density over time to signal increasing danger or psychological shifts.
tween.tween_property(env, "volumetric_fog_density", current_density + target_density, 5.0)
# horror_patterns.gd
extends Node
# 1. High-Performance Monster Line-of-Sight (Stealth)
# Bypasses Area3D for immediate, physics-synced raycasting.
func check_monster_los(monster: CharacterBody3D, player: Node3D) -> bool:
var space_state := monster.get_world_3d().direct_space_state
var query := PhysicsRayQueryParameters3D.create(monster.global_position, player.global_position)
# Exclude the monster's own RID from the raycast to prevent self-intersection
query.exclude = [monster.get_rid()]
var result := space_state.intersect_ray(query)
return not result.is_empty() and result.collider == player
# 2. Procedural Flashlight Flicker (Light/Dark)
# Uses Tween for lightweight parameter manipulation.
var _flicker_tween: Tween
func trigger_flashlight_flicker(light: SpotLight3D) -> void:
if _flicker_tween:
_flicker_tween.kill()
_flicker_tween = create_tween().set_loops(4)
# Smoothly animates the light energy property to simulate failing batteries.
_flicker_tween.tween_property(light, "light_energy", 0.0, 0.05)
_flicker_tween.tween_property(light, "light_energy", 2.5, 0.1)
# 3. Strictly Typed Inventory Data Dictionary (RE/Silent Hill)
# Decouples inventory logic from the visual UI grid.
# Uses StringName for optimized lookups and custom Resources for item data.
var inventory: Dictionary[StringName, Resource] = {
&"mansion_key": null,
&"handgun_ammo": null
}
# 4. Asynchronous Jump-Scare Loading (Optimization)
# Prevents the game thread from freezing right before a scare.
const SCARE_PATH := "res://scares/hallway_ghost.tscn"
func preload_jump_scare() -> void:
ResourceLoader.load_threaded_request(SCARE_PATH)
func execute_jump_scare() -> void:
var scare_scene := ResourceLoader.load_threaded_get(SCARE_PATH) as PackedScene
if scare_scene:
get_tree().root.add_child(scare_scene.instantiate())
# 5. Physics-Based Noise Radius Query (Stealth)
# Instantly detects all listening entities within a radius without relying on physics nodes.
func emit_noise(origin: Transform3D, radius_rid: RID) -> void:
var query := PhysicsShapeQueryParameters3D.new()
query.shape_rid = radius_rid
query.transform = origin
# Executes the spatial query directly in the C++ physics server.
var overlaps := get_world_3d().direct_space_state.intersect_shape(query)
for hit in overlaps:
if hit.collider.has_method(&"investigate_noise"):
hit.collider.investigate_noise(origin.origin)
# 6. Deep Duplication of Inventory Items (State Management)
# Ensures identical items (like two handguns) can track ammo independently.
func add_item_to_inventory(base_item_resource: Resource) -> void:
# DEEP_DUPLICATE_ALL forces subresources (like magazines) to be unique.
var unique_item := base_item_resource.duplicate(true) # duplicate(true) is deep in G4
# _inventory_array.append(unique_item)
# 7. Dynamically Adjusting Volumetric Fog (Atmosphere)
# Increases the claustrophobia effect by manipulating the environment.
func intensify_fog(env: Environment) -> void:
# Set this to the lowest base density you want globally.
var base_density := env.volumetric_fog_density
var tween := create_tween()
tween.tween_property(env, "volumetric_fog_density", base_density + 0.15, 2.0)
# 8. Suspending Off-Screen AI (Optimization)
# Connected to a VisibleOnScreenNotifier3D.
func _on_screen_exited() -> void:
# Disabling physics processing reclaims CPU cycles when the monster isn't visible.
set_physics_process(false)
func _on_screen_entered() -> void:
set_physics_process(true)
# 9. Shader Uniform Manipulation for Hallucinations (Psychological)
# Passes runtime sanity values directly into the RenderingServer.
func update_sanity_visuals(mesh_instance: GeometryInstance3D, current_sanity: float) -> void:
# Directly updates the shader without needing to duplicate the material.
mesh_instance.set_instance_shader_parameter(&"hallucination_intensity", 1.0 - current_sanity)
# 10. Advanced State Machine Pattern Matching (Monster AI)
# Uses Godot 4's powerful match statement with optimized StringNames.
var _current_state: StringName = &"idle"
func _physics_process(delta: float) -> void:
match _current_state:
&"patrol":
# _process_patrol(delta)
pass
&"chase":
# _process_chase(delta)
pass
&"search":
# _process_search(delta)
pass
_:
# push_error("Invalid AI sproceedtected.")
pass
# inventory_data_storage.gd
extends Node
# Strictly Typed Inventory Data Dictionary (RE/Silent Hill Style)
# Decouples inventory logic from the visual UI grid to ensure data integrity.
# Uses StringName (&"name") for optimized pointer-level lookups in the dictionary.
var inventory: Dictionary[StringName, Resource] = {
&"mansion_key": null,
&"handgun_ammo": null
}
func has_item(item_id: StringName) -> bool:
return inventory.has(item_id) and inventory[item_id] != null
# item_state_duplicator.gd
extends Node
# Deep Duplication of Inventory Items (State Management)
# Ensures identical items (like two handguns) can track ammo/durability independently
# by breaking the shared resource link.
func add_unique_item(base_item_resource: Resource) -> Resource:
# duplicate(true) performs a deep copy of sub-resources.
# In Godot 4, this ensures unique magazines/attachments for each instance.
var unique_item := base_item_resource.duplicate(true)
return unique_item
# monster_los_check.gd
extends Node
# High-Performance Monster Line-of-Sight (Stealth)
# Bypasses Area3D for immediate, physics-synced raycasting via DirectSpaceState.
func check_monster_los(monster: CharacterBody3D, player: Node3D) -> bool:
var space_state := monster.get_world_3d().direct_space_state
var query := PhysicsRayQueryParameters3D.create(monster.global_position, player.global_position)
# EXTREMELY IMPORTANT: Exclude the monster's own RID from the raycast
# to prevent immediate self-intersection which returns false positives.
query.exclude = [monster.get_rid()]
var result := space_state.intersect_ray(query)
# Returns true only if the ray hits the player without obstruction.
return not result.is_empty() and result.collider == player
# offscreen_logic_suspender.gd
extends Node
# Suspending Off-Screen AI (CPU Optimization)
# Used in conjunction with VisibleOnScreenNotifier3D to save cycles.
func _on_screen_exited() -> void:
# Disabling physics processing reclaims CPU cycles when the monster isn't visible.
# CRITICAL: Ensure visual-only effects (sound cues) handle this state carefully.
set_physics_process(false)
func _on_screen_entered() -> void:
# Resume immediately when entering player frustum.
set_physics_process(true)
# optimized_horror_state_machine.gd
extends Node
# Advanced State Machine Pattern Matching (Monster AI)
# Uses Godot 4's high-speed Enum/StringName matching for monster behavior.
var _active_state: StringName = &"patrol"
func _physics_process(_delta: float) -> void:
match _active_state:
# StringNames are pointer-compared, far faster than standard string hashing.
&"patrol":
_handle_patrol()
&"chase":
_handle_chase()
&"search":
_handle_search()
_:
# Fallback for undefined states.
pass
func _handle_patrol() -> void: pass
func _handle_chase() -> void: pass
func _handle_search() -> void: pass
# godot-master/scripts/horror_predator_stalking_ai.gd
extends CharacterBody2D
## Predator Stalking AI Expert Pattern
## AI that maintains pursuit while actively avoiding the player's view cone.
@export var player: CharacterBody2D
@export var stalking_distance: float = 300.0
@export var movement_speed: float = 100.0
func _physics_process(_delta: float) -> void:
if not player: return
var dir_to_player = global_position.direction_to(player.global_position)
var dist_to_player = global_position.distance_to(player.global_position)
# 1. Line of Sight (LOS) Check
var is_player_looking = _check_if_player_is_looking()
var target_pos = global_position
if is_player_looking:
# 2. Hide/Retreat Pattern
# Move toward a position that is outside the player's view cone or behind cover.
target_pos = _find_hiding_spot()
else:
# 3. Follow/Stalk Pattern
if dist_to_player > stalking_distance:
target_pos = player.global_position - (dir_to_player * (stalking_distance * 0.8))
velocity = global_position.direction_to(target_pos) * movement_speed
move_and_slide()
func _check_if_player_is_looking() -> bool:
# Check if the AI is within the player's forward cone (e.g. 60 degrees)
var player_forward = -player.global_transform.y # Assuming Y-up/Forward in 2D
var dir_to_ai = player.global_position.direction_to(global_position)
var dot = player_forward.dot(dir_to_ai)
return dot > 0.5 # Within ~60 degree cone
func _find_hiding_spot() -> Vector2:
# Simplified: Move perpendicular to the player's view to get out of sight quickly.
var player_forward = -player.global_transform.y
var side_dir = Vector2(-player_forward.y, player_forward.x)
return global_position + (side_dir * 100.0)
## EXPERT NOTE:
## For true 'Stalking' feel, use NavigationRegion2D to find points that
## have NO occlusion to the player, but are within a specific distance.
# skills/genre-horror/scripts/sanity_manager.gd
extends Node
## Sanity Manager (Expert Pattern)
## Tracks sanity and applies global effects (audio distortion, camera shake).
class_name SanityManager
@export var max_sanity: float = 100.0
@export var decay_rate: float = 0.5
@export var world_environment: WorldEnvironment
@export var player_camera: Camera3D
var current_sanity: float = 100.0
func _process(delta: float) -> void:
# Decay when in darkness (example logic)
current_sanity -= decay_rate * delta
current_sanity = clamp(current_sanity, 0.0, max_sanity)
_apply_effects()
func _apply_effects() -> void:
var stress_factor = 1.0 - (current_sanity / max_sanity)
# 1. Audio Distortion (Low Pass)
var bus_idx = AudioServer.get_bus_index("Master")
var effect = AudioServer.get_bus_effect(bus_idx, 0) # Assumes Effect 0 is LowPass/Distortion
if effect is AudioEffectLowPassFilter:
effect.cutoff_hz = lerp(20000.0, 500.0, stress_factor)
# 2. Camera Shake (Continuous jitter)
if player_camera and stress_factor > 0.5:
var shake = (stress_factor - 0.5) * 0.1
player_camera.h_offset = randf_range(-shake, shake)
player_camera.v_offset = randf_range(-shake, shake)
else:
if player_camera:
player_camera.h_offset = 0
player_camera.v_offset = 0
func recover(amount: float) -> void:
current_sanity += amount
## EXPERT USAGE:
## Add AudioEffectLowPassFilter to Master bus slot 0.
## Adjust decay logic based on light/proximity to monsters.
# sanity_shader_manager.gd
extends Node
# Shader Uniform Manipulation for Hallucinations (Psychological Horror)
# Passes runtime sanity values directly into mesh instances without material duplication.
func update_sanity_visuals(mesh: GeometryInstance3D, current_sanity: float) -> void:
# set_instance_shader_parameter is highly optimized and avoids per-mesh material copies.
# Note: Requires the shader to have a 'hallucination_intensity' uniform.
var intensity = clamp(1.0 - current_sanity, 0.0, 1.0)
mesh.set_instance_shader_parameter(&"hallucination_intensity", intensity)
# spatial_noise_emitter.gd
extends Node
# Physics-Based Noise Radius Query (Stealth/Sensing)
# Instantly detects all listening entities within a radius without relying
# on heavy persistent collision signals or Area3D nodes.
func emit_noise(origin: Transform3D, noise_shape_rid: RID) -> void:
var query := PhysicsShapeQueryParameters3D.new()
query.shape_rid = noise_shape_rid
query.transform = origin
# Executes the spatial query directly in the C++ physics server for sub-millisecond response.
var overlaps := get_world_3d().direct_space_state.intersect_shape(query)
for hit in overlaps:
# Check for modular 'listener' component or detection method.
if hit.collider.has_method(&"investigate_noise"):
hit.collider.investigate_noise(origin.origin)