6.7 KiB
NPC Line-of-Sight (LOS) System Documentation
Overview
The NPC Line-of-Sight (LOS) system allows NPCs to detect the player and events (like lockpicking) only when the player is within a configurable vision cone. This adds realism to NPC perception and prevents event triggering when NPCs can't "see" the player.
Configuration
NPC JSON Structure
Add a los property to any NPC definition:
{
"id": "security_guard",
"npcType": "person",
"los": {
"enabled": true,
"range": 300,
"angle": 140,
"visualize": false
},
"eventMappings": [
{
"eventPattern": "lockpick_used_in_view",
"targetKnot": "on_lockpick_used",
"conversationMode": "person-chat",
"cooldown": 0
}
]
}
LOS Properties
| Property | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true | Whether LOS detection is active |
range |
number | 300 | Detection range in pixels |
angle |
number | 120 | Field of view angle in degrees (120° = 60° on each side of facing direction) |
visualize |
boolean | false | Whether to render the LOS cone for debugging |
How It Works
Detection Algorithm
- Distance Check: Player must be within
rangepixels of NPC - Angle Check: Player must be within
angledegrees of NPC's facing direction - Both conditions required: Player must satisfy both distance AND angle constraints
Facing Direction
The system automatically detects NPC facing direction from:
- Explicit
facingDirectionproperty (if set on NPC instance) - Sprite rotation (converted from radians to degrees)
- Direction property (0=down, 1=left, 2=up, 3=right)
- Default: 270° (facing up)
For NPCs with patrol routes, the facing direction updates based on current movement direction.
Implementation Details
Files
-
js/systems/npc-los.js- Core LOS detection and visualizationisInLineOfSight(npc, target, losConfig)- Check if target is in LOSdrawLOSCone(scene, npc, losConfig)- Render debug visualizationclearLOSCone(graphics)- Clean up graphics
-
js/systems/npc-manager.js- NPC manager integrationshouldInterruptLockpickingWithPersonChat(roomId, playerPosition)- Check if any NPC can see the player attempting to lockpicksetLOSVisualization(enable, scene)- Toggle LOS cone renderingupdateLOSVisualizations(scene)- Update cone graphics (call from game loop)
-
js/systems/unlock-system.js- Integration with lock system- Passes player position when checking for NPC interruption
Integration with Lockpicking
When a player attempts to lockpick:
// unlock-system.js checks LOS before starting minigame
const playerPos = window.player.sprite.getCenter();
const interruptingNPC = window.npcManager.shouldInterruptLockpickingWithPersonChat(roomId, playerPos);
if (interruptingNPC) {
// NPC can see player - trigger person-chat instead of lockpicking
// emit lockpick_used_in_view event
return; // Don't start lockpicking
}
// Otherwise, proceed with normal lockpicking
Usage
Checking LOS in Code
import { isInLineOfSight } from 'js/systems/npc-los.js';
const losConfig = {
range: 300,
angle: 120,
enabled: true
};
const canSee = isInLineOfSight(npc, playerPosition, losConfig);
if (canSee) {
console.log('NPC can see player!');
}
Debugging with Visualization
Enable LOS cone rendering to visualize NPC vision:
// In console or during game init
window.npcManager.setLOSVisualization(true, window.game.scene.scenes[0]);
// Then call from game loop (in update method)
window.npcManager.updateLOSVisualizations(window.game.scene.scenes[0]);
The visualization shows:
- Green semi-transparent cone = NPC's field of view
- Cone origin = NPC's position
- Cone angle = Configured
angleproperty - Cone range = Configured
rangeproperty
Server Migration Notes
Since this system is client-side only, consider:
- Phase 1 (Current): Client-side LOS checks for cosmetic reactions
- Phase 2 (Future): Server validates LOS before accepting unlock attempts
- Migration Path: Keep client-side system for immediate feedback, server validates actual event
Testing
Test Scenario
The file scenarios/npc-patrol-lockpick.json includes two NPCs with LOS configured:
"security_guard": {
"los": {
"enabled": true,
"range": 300,
"angle": 140,
"visualize": false
},
...
}
Test Cases
- In LOS: Player stands in front of NPC within range → NPC reacts to lockpicking
- Out of Range: Player stands far away → NPC does NOT react
- Behind NPC: Player behind NPC's facing direction → NPC does NOT react
- Partial Angle: Player at edge of FOV cone → Reacts only if within angle bounds
Running Tests
// Enable LOS visualization
window.npcManager.setLOSVisualization(true, window.game.scene.scenes[0]);
// Manually test LOS
const playerPos = window.player.sprite.getCenter();
const security_guard = window.npcManager.getNPC('security_guard');
const canSee = window.npcManager.shouldInterruptLockpickingWithPersonChat('patrol_corridor', playerPos);
console.log('Can NPC see player?', canSee !== null);
Performance Considerations
- LOS checks: O(n) where n = number of NPCs in room (very fast)
- Distance calculation: Uses Phaser's
Distance.Between()(optimized) - Visualization: Only enabled for debugging, should be disabled in production
- Angle calculation: Minimal overhead, only done when needed
Common Issues
Issue: NPC always sees player
- Check: Verify
los.enabled: truein NPC definition - Check: Confirm
rangeis large enough for test scenario - Check: Verify
anglevalue is correct (should be 120-180 for typical coverage)
Issue: NPC never sees player
- Check: Player position is correct (check
window.player.sprite.getCenter()) - Check: NPC position is correct
- Check: NPC facing direction is correct (check
npc.directionornpc.facingDirection) - Debug: Enable visualization with
setLOSVisualization(true, scene)
Issue: Visualization cone not showing
- Check:
visualizeproperty is set totrue(or always enabled viasetLOSVisualization) - Check: Scene is passed correctly to
updateLOSVisualizations() - Check: Call
updateLOSVisualizations()from game's update loop
Future Enhancements
- Obstacles: Add wall/terrain blocking for more realistic LOS
- Hearing: Add audio-based detection (separate system)
- Memory: Add NPC memory of recent player sightings
- Alert Levels: Different LOS ranges based on NPC alert state
- Dynamic Facing: Update facing direction based on patrol waypoints