Lesson 29
Stealth mechanics: detection should feel earned, not random.
A stealth game is a promise: if the player understands vision, sound and patrols, they can plan. Hidden rules break that promise.
1. Patrol routes are moving waypoints
Patrols are pathfinding with personality. A guard follows waypoints, pauses, looks around, then continues. This is more readable than random wandering because the player can observe and learn the pattern.
function updatePatrol(guard, dt) {
const target = guard.route[guard.routeIndex];
const dx = target.x - guard.x;
const dy = target.y - guard.y;
const dist = Math.hypot(dx, dy) || 1;
if (dist < 4) {
guard.wait -= dt;
if (guard.wait <= 0) {
guard.routeIndex = (guard.routeIndex + 1) % guard.route.length;
guard.wait = guard.pauseTime || 0.6;
}
return;
}
guard.x += dx / dist * guard.speed * dt;
guard.y += dy / dist * guard.speed * dt;
guard.angle = Math.atan2(dy, dx);
}
Source pattern: Supagames enemy AI lessons use explicit enemy movement and state; stealth extends that with patrol routes, vision checks and suspicion instead of direct chasing only.
2. Vision cones need distance and angle
A simple vision cone checks two things: is the player close enough, and is the player within the guard's facing angle? This can be done with angle difference. Add wall checks later if your map needs it.
function angleDiff(a, b) {
let d = (a - b + Math.PI) % (Math.PI * 2) - Math.PI;
return Math.abs(d);
}
function canSeePlayer(guard, player) {
const dx = player.x - guard.x;
const dy = player.y - guard.y;
const dist = Math.hypot(dx, dy);
if (dist > guard.visionRange) return false;
const angleToPlayer = Math.atan2(dy, dx);
return angleDiff(angleToPlayer, guard.angle) < guard.visionAngle / 2;
}
Draw the cone during development and maybe during gameplay. Stealth is much fairer when the player can see danger zones.
3. Suspicion is better than instant failure
Instant detection can be harsh, especially on mobile. Suspicion gives the player a short recovery window. If the player stays visible, suspicion fills and the alarm triggers. If the player breaks line of sight, it drains.
function updateSuspicion(guard, player, dt) {
if (canSeePlayer(guard, player) && !player.hidden) {
guard.suspicion += dt * guard.alertRate;
} else {
guard.suspicion -= dt * guard.calmRate;
}
guard.suspicion = Math.max(0, Math.min(1, guard.suspicion));
if (guard.suspicion >= 1) triggerAlarm(guard);
}
A suspicion meter also creates tension. The player sees the mistake forming and can react before failure.
4. Noise creates risk without sight
Sound lets stealth levels react to running, breaking objects, opening doors or fighting. Keep noise simple: events have position and radius. Guards inside the radius investigate the location.
function emitNoise(x, y, radius) {
for (const guard of guards) {
const d = Math.hypot(guard.x - x, guard.y - y);
if (d < radius) {
guard.state = "investigate";
guard.investigateTarget = { x, y };
guard.suspicion = Math.max(guard.suspicion, 0.35);
}
}
}
Noise should be teachable. If sprinting makes noise, show a footstep icon or ripple. If throwing an item distracts guards, show the landing sound.
5. Hiding spots need clear rules
A hiding spot can be a bush, shadow, locker or crowd. The player should know when they are hidden. Set a boolean based on overlap and maybe require crouch or interact. Do not make hidden state ambiguous.
function updateHiding(player, hidingZones) {
player.hidden = hidingZones.some(zone => {
return player.x > zone.x && player.x < zone.x + zone.w &&
player.y > zone.y && player.y < zone.y + zone.h &&
player.crouching;
});
}
Visual feedback can be as simple as a darker outline or "Hidden" label. The player must trust the system.
6. Alarm state changes the whole level
When the alarm triggers, guards can chase, doors can lock, timers can start or reinforcements can spawn. Keep the alarm state explicit so every system can read it.
function triggerAlarm(sourceGuard) {
state.alarm = true;
state.alarmTimer = 12;
for (const guard of guards) {
guard.state = "chase";
guard.target = player;
guard.suspicion = 1;
}
addMessage(sourceGuard.name + " raised the alarm!");
}
Stealth games are strongest when failure changes the plan instead of immediately ending the run. Chase, escape and recovery can be more interesting than a hard game over.
7. Design stealth rooms as readable puzzles
A stealth room should have a clear entrance, visible objective, at least one safe observation spot and at least one recovery route. Before adding complex AI, sketch the player's intended path: wait, cross, hide, distract, collect, exit. Then play the room without using perfect timing. If success requires frame-perfect movement, the puzzle is probably too strict for a browser game and definitely too strict for touch controls.
Use color and animation to teach danger. Yellow can mean suspicious, red can mean alarm, blue can mean hidden. Patrol pauses should be visible, not hidden inside timing math. The more readable the room is, the more clever the player feels when they beat it.