godot-genre-stealth
0
总安装量
3
周安装量
安装命令
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-genre-stealth
Agent 安装分布
opencode
3
gemini-cli
3
codex
3
claude-code
2
github-copilot
2
Skill 文档
Genre: Stealth
Player choice, systemic AI, and clear communication define stealth games.
Available Scripts
stealth_ai_controller.gd
Expert AI controller with graduated detection, sound response, and alert state management.
Core Loop
Observe â Plan â Execute â Adapt â Complete
NEVER Do in Stealth Games
- NEVER use instant binary detection â Gradual 0-100% detection with visual feedback (filling meter). Binary “seen/not seen” removes player agency and feels unfair.
- NEVER make guards see through walls â Raycast-based vision with collision masks.
has_line_of_sight()must check geometry. Wallhacks destroy stealth integrity. - NEVER use simple distance checks for sound â Sound propagates along
NavigationServer3Dpaths, NOT straight-line distance. Through-wall hearing breaks immersion. –NEVER make combat as viable as stealth â If guns are easier than sneaking, players ignore stealth. Combat should be risky (outnumbered, limited ammo, loud alerts). - NEVER hide detection reasons from player â Show WHY detected (light level high, made noise, in vision cone). “Gotcha” deaths frustrate, don’t teach.
- NEVER use single sample point for player visibility â Sample multiple body parts (head, torso, feet). Hiding behind low cover should hide torso but expose head.
- NEVER forget peripheral vision â Humans have ~180° peripheral (less effective) + 60° focused vision. Single cone = unrealistic. Use composite shapes (Splinter Cell method).
Design Principles
From industry experts (Splinter Cell, Dishonored, Hitman developers):
- Player Choice: Multiple valid approaches to every scenario
- Systemic Design: Rules-based AI that players can learn and exploit
- Clear Communication: Player always understands game state and threats
- Fair Detection: No “gotcha” moments – threats visible before dangerous
AI Detection System
Vision Cone Implementation
Based on Splinter Cell Blacklist GDC talk – realistic vision uses composite shapes:
class_name EnemyVision
extends Node3D
@export var forward_vision_range := 20.0 # Main vision cone
@export var peripheral_range := 10.0 # Side vision
@export var forward_fov := 60.0 # Degrees
@export var peripheral_fov := 120.0 # Degrees
@export var detection_speed := 1.0 # How fast detection builds
var detection_level := 0.0 # 0-100
var target: Node3D = null
func _physics_process(delta: float) -> void:
var player := get_player_if_visible()
if player:
# Detection rate varies by:
# - Distance (closer = faster)
# - Lighting on player
# - Player movement (moving = more visible)
# - In peripheral vs direct vision
var rate := calculate_detection_rate(player)
detection_level = min(100, detection_level + rate * delta)
else:
detection_level = max(0, detection_level - detection_speed * 0.5 * delta)
func get_player_if_visible() -> Player:
var player := get_tree().get_first_node_in_group("player")
if not player:
return null
var to_player := player.global_position - global_position
var distance := to_player.length()
var angle := rad_to_deg(global_basis.z.angle_to(-to_player.normalized()))
# Check forward cone
if angle < forward_fov / 2.0 and distance < forward_vision_range:
if has_line_of_sight(player):
return player
# Check peripheral (less effective)
elif angle < peripheral_fov / 2.0 and distance < peripheral_range:
if has_line_of_sight(player):
return player
return null
func calculate_detection_rate(player: Player) -> float:
var distance := global_position.distance_to(player.global_position)
var distance_factor := 1.0 - (distance / forward_vision_range)
var light_factor := player.get_light_level() # 0.0 = dark, 1.0 = lit
var movement_factor := 1.0 if player.velocity.length() > 0.5 else 0.3
return detection_speed * distance_factor * light_factor * movement_factor * 50.0
Sound Detection System
Based on Thief/Hitman implementation – sounds propagate along navigation paths:
class_name SoundPropagation
extends Node
# Sound travels through connected navigation points, not through walls
func propagate_sound(origin: Vector3, loudness: float, sound_type: String) -> void:
for enemy in get_tree().get_nodes_in_group("enemies"):
var path := NavigationServer3D.map_get_path(
get_world_3d().navigation_map,
origin,
enemy.global_position,
true
)
if path.is_empty():
continue # No path = sound blocked
var path_distance := calculate_path_length(path)
var heard_loudness := loudness - (path_distance * 0.5) # Falloff
if heard_loudness > enemy.hearing_threshold:
enemy.hear_sound(origin, sound_type, heard_loudness)
func calculate_path_length(path: PackedVector3Array) -> float:
var length := 0.0
for i in range(1, path.size()):
length += path[i].distance_to(path[i - 1])
return length
Player Light Level
class_name LightDetector
extends Node3D
@export var sample_points: Array[Marker3D] # Multiple points on player body
func get_light_level() -> float:
var total := 0.0
var space := get_world_3d().direct_space_state
for point in sample_points:
for light in get_tree().get_nodes_in_group("lights"):
var dir := light.global_position - point.global_position
var query := PhysicsRayQueryParameters3D.create(
point.global_position,
light.global_position
)
var result := space.intersect_ray(query)
if result.is_empty(): # Not blocked
total += light.light_energy / dir.length_squared()
return clamp(total / sample_points.size(), 0.0, 1.0)
AI Alert States
Three-phase system (industry standard):
enum AlertState { IDLE, SUSPICIOUS, ALERTED, COMBAT }
class_name EnemyAI
extends CharacterBody3D
var alert_state := AlertState.IDLE
var suspicion_point: Vector3
var search_timer := 0.0
signal alert_state_changed(new_state: AlertState)
func transition_to(new_state: AlertState) -> void:
alert_state = new_state
alert_state_changed.emit(new_state)
match new_state:
AlertState.SUSPICIOUS:
play_animation("suspicious")
speak_dialogue("what_was_that")
AlertState.ALERTED:
speak_dialogue("who_goes_there")
# Other guards in range hear and become suspicious
alert_nearby_guards()
AlertState.COMBAT:
speak_dialogue("intruder")
trigger_alarm()
Visual Feedback (Critical!)
class_name AlertIndicator
extends Node3D
@export var idle_icon: Texture2D
@export var suspicious_icon: Texture2D # "?"
@export var alerted_icon: Texture2D # "!"
@export var detection_meter: ProgressBar # Shows filling detection
func update_indicator(state: AlertState, detection: float) -> void:
detection_meter.value = detection
match state:
AlertState.IDLE:
icon.texture = idle_icon
detection_meter.visible = false
AlertState.SUSPICIOUS:
icon.texture = suspicious_icon
detection_meter.visible = true
AlertState.ALERTED:
icon.texture = alerted_icon
detection_meter.visible = false
Player Abilities
Five categories of stealth tools (per Mark Brown’s analysis):
1. Movement Alteration
# Crouch, crawl, run (noisy vs quiet)
func calculate_noise_level() -> float:
if is_crouching:
return 0.2
elif is_running:
return 1.0
else:
return 0.5
2. Information Gathering
# Peek, scout, mark enemies
func activate_detective_vision() -> void:
for enemy in get_tree().get_nodes_in_group("enemies"):
enemy.show_outline()
enemy.show_vision_cone()
3. AI Manipulation
# Throw distractions
func throw_distraction(target_position: Vector3) -> void:
var rock := distraction_scene.instantiate()
rock.global_position = target_position
add_child(rock)
SoundPropagation.propagate_sound(target_position, 30.0, "impact")
4. Space Control
# Shoot out lights, create hiding spots
func shoot_light(light: Light3D) -> void:
light.visible = false
# Update light level for area
5. Enemy Elimination
func perform_takedown(enemy: EnemyAI, lethal: bool) -> void:
if enemy.alert_state == AlertState.COMBAT:
return # Can't stealth kill alert enemy
if lethal:
enemy.die()
else:
enemy.knockout()
# Body becomes interactable
spawn_body(enemy)
Level Design
Outpost Design (Open Areas)
[Safe perimeter for observation]
|
[Sparse guards at edges - isolatable]
|
[Dense center with objective]
|
[Multiple entry points/routes]
Limited Encounter Design (Corridors)
- Enemies visible 8+ meters before engagement
- Multiple paths through
- Cover objects and hiding spots
- Emergency escape routes
UI Communication
Based on Thief’s “light gem” innovation:
class_name StealthHUD
extends Control
@onready var visibility_meter: TextureProgressBar
@onready var sound_meter: TextureProgressBar
@onready var minimap: Control
func _process(_delta: float) -> void:
visibility_meter.value = player.get_light_level() * 100
sound_meter.value = player.current_noise_level * 100
Common Pitfalls
| Pitfall | Solution |
|---|---|
| Instant detection | Use gradual detection with clear feedback |
| Guards see through walls | Raycast-based vision with proper collision |
| Unfair patrol patterns | Make patterns learnable, with tells |
| Two games (stealth + combat) | Either commit to stealth or make combat risky |
| Unclear detection | Always show WHY player was detected |
Godot-Specific Tips
- Raycasts for vision: Use
PhysicsRayQueryParameters3Dwith collision masks - NavigationAgent3D: For patrol routes and pathfinding
- Area3D: For sound propagation zones and trigger areas
- AnimationTree: Blend between alert state animations
Reference
- Master Skill: godot-master