An FSM-based AI system that dynamically adjusts aggression based on player velocity and navigates a segmented maze environment.
The AI evaluates Player Speed (for aggression) and Light Exposure (for fleeing), while maintaining pathing through complex room segments.
FSM States:
• Stalking (default pursuit state)
• HighAggression (anti-camping pressure)
• Fleeing (light avoidance behavior)
The environment consists of distinct rectangular rooms connected by narrow door frames. A major technical hurdle was the AI losing the player when traversing between these segments.
The Problem: When the player passed through a door, the MoveTo request would sometimes fail or cancel because the direct path was broken by door frame geometry, causing the Ghost to stop “flowing” after the player.
The Solution: I optimized NavMesh Query settings to increase tile resolution around doorframes. I also implemented a Last Known Location memory so the AI continues to the doorway if line-of-sight is temporarily lost.
To prevent players from bypassing tension by camping in safe corners, the AI’s aggression is directly driven by the Player’s Velocity Vector.
// Ramping aggression if player is camping
FVector PlayerVel = TargetPlayer->GetVelocity();
if (PlayerVel.Size() < 10.f)
{
TimeSinceStopped += DeltaTime;
if (TimeSinceStopped > 2.0f)
{
// Enrage Mode
GetCharacterMovement()->MaxWalkSpeed = 600.f;
}
}
else
{
TimeSinceStopped = 0.f;
GetCharacterMovement()->MaxWalkSpeed = DefaultSpeed;
}
The Ghost’s primary weakness is light. A vector math check determines whether the Ghost is inside the player’s flashlight cone.
// Check if Ghost is inside flashlight cone
FVector ToGhost = (GetActorLocation() - Player->GetActorLocation()).GetSafeNormal();
FVector LightForward = Player->GetFirstPersonCamera()->GetForwardVector();
float LightDot = FVector::DotProduct(LightForward, ToGhost);
if (LightDot > 0.7f && Player->IsFlashlightOn())
{
EnterState(EAIState::Fleeing);
}
The result is an AI system that maintains tension organically by responding directly to player intent rather than scripted triggers.
Passive play is punished through escalating aggression, rapid movement increases pressure, and light becomes a tactical tool instead of a guaranteed escape. This creates a feedback loop where the player’s own behavior drives the intensity of the encounter.
By combining simple sensory checks, navigation memory, and FSM-driven decision-making, the system produces emergent horror without relying on jump scares or hard-coded events.