Hello! Welcome back to our course on Finite State Machines.
In the previous lesson, you built a complete combat loop for your NPC, implementing the Attack state and a Cooldown state. Your NPC can now patrol, chase down a player, engage in combat, and return to its duties. This is a fantastic foundation for a game AI.
This lesson addresses the final learning outcome in our NPC AI module: Implement a 'Flee' state triggered by a low health condition, using PathfindingService to move to a safe point.
We'll be adding a layer of self-preservation to the NPC. Instead of fighting to the bitter end, it will recognize when it's losing and try to escape. This is a common behavior that makes AI seem more intelligent and believable.
Just like the ghosts in Pac-Man have a state for running away when Pac-Man eats a power pill, our NPC will have a state for running away when its health gets too low.

1. The Trigger: When to Flee?
First, we need to decide when the NPC should flee. The trigger is low health. We could check the NPC's health in every state's Update function, but this is inefficient and repetitive. A much better approach in Roblox is to use the Humanoid.HealthChanged event. This event fires only when the health value changes, making it the perfect trigger for our transition.
Let's define a health threshold. If the NPC's health drops below this, it's time to run.
Exercise: Set up the Health-Based Trigger
-
Add a new configuration variable at the top of your NPC script:
local FLEE_HEALTH_THRESHOLD = 30 -- The health percentage below which the NPC will flee -
At the end of your script, after you've defined the
humanoidvariable, add the following code to listen for health changes. This logic should exist outside any specific state.-- Listen for health changes to trigger the Flee state humanoid.HealthChanged:Connect(function(currentHealth) local healthPercentage = (currentHealth / humanoid.MaxHealth) * 100 -- Only trigger if health is low AND we are not already fleeing if healthPercentage <= FLEE_HEALTH_THRESHOLD and fsm:getCurrentState() ~= "Flee" then print("Health is critical! Transitioning to Flee state.") fsm:transitionTo("Flee") end end)
This event connection ensures that no matter what state the NPC is in—Patrol, Chase, or Attack—it will immediately try to flee if its health becomes critical.
2. The Destination: Where to Flee?
An NPC fleeing needs a destination. For this exercise, we'll create a set of pre-defined "safe points" in our game world. When the NPC decides to flee, it will find the closest safe point and run towards it.
Exercise: Create Safe Points
- In the Roblox Studio Explorer, create a
Folderinside theWorkspace. - Name this folder
SafePoints. - Inside this folder, create a few
Parts. These will be your safe locations. - Position these parts around your map. Good locations might be behind buildings, in alleys, or in areas far from the main patrol route.
- Make sure these parts are Anchored and their CanCollide property is turned off. They are just markers, not physical objects.
An example setup with a 'SafePoints' folder in the Workspace containing several designated safe location parts.
3. The Implementation: The Flee State
Now we'll build the fleeState itself. Its job is to find the nearest safe point and use PathfindingService to get there. Since you've already implemented pathfinding for the Patrol and Chase states, the process will be very familiar.
Exercise: Implement the Flee State
First, add the new state to your states table.
local states = {
Idle = idleState,
Patrol = patrolState,
Chase = chaseState,
Attack = attackState,
Cooldown = cooldownState,
Flee = fleeState, -- Add this line
}
Now, let's define fleeState. Add this code block alongside your other state definitions.
-- Define where to find the safe points
local safePointsFolder = workspace:WaitForChild("SafePoints")
local fleeState = {
path = nil,
waypoints = nil,
currentWaypointIndex = 0,
connections = {},
Enter = function()
print("NPC entered Flee state.")
-- 1. Find the closest safe point
local closestPoint, minDistance = nil, math.huge
for _, point in ipairs(safePointsFolder:GetChildren()) do
local distance = (npc.PrimaryPart.Position - point.Position).Magnitude
if distance < minDistance then
minDistance = distance
closestPoint = point
end
end
if not closestPoint then
warn("No safe points found! NPC cannot flee. Returning to Idle.")
fsm:transitionTo("Idle")
return
end
print("Fleeing towards safe point: " .. closestPoint.Name)
-- 2. Compute the path to the safe point
fleeState.path = PathfindingService:CreatePath()
local success, errorMessage = pcall(function()
fleeState.path:ComputeAsync(npc.PrimaryPart.Position, closestPoint.Position)
end)
if not success or fleeState.path.Status ~= Enum.PathStatus.Success then
warn("Pathfinding failed to find a path to the safe point.", errorMessage)
fsm:transitionTo("Idle") -- Fallback if no path can be found
return
end
-- 3. Start following the path
fleeState.waypoints = fleeState.path:GetWaypoints()
fleeState.currentWaypointIndex = 1 -- Start at the first waypoint
-- Connect events for movement
fleeState.connections.reached = humanoid.MoveToFinished:Connect(fleeState.onWaypointReached)
fleeState.connections.blocked = fleeState.path.Blocked:Connect(fleeState.onPathBlocked)
-- Move to the first waypoint
fleeState.onWaypointReached(true)
end,
onWaypointReached = function(reached)
-- Check if movement was successful and if there are more waypoints
if reached and fleeState.currentWaypointIndex < #fleeState.waypoints then
fleeState.currentWaypointIndex += 1
local waypoint = fleeState.waypoints[fleeState.currentWaypointIndex]
humanoid:MoveTo(waypoint.Position)
if waypoint.Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
elseif reached then
-- We've reached the final waypoint
print("NPC reached the safe point. Hiding for a moment.")
fleeState.Exit() -- Manually call exit to clean up
-- Wait for a few seconds before returning to patrol
task.wait(5)
if fsm:getCurrentState() == "Flee" then -- Check if we're still in this state
fsm:transitionTo("Patrol")
end
end
end,
onPathBlocked = function(blockedWaypointIndex)
-- If the path ahead is blocked, re-calculate the path
if blockedWaypointIndex >= fleeState.currentWaypointIndex then
print("Flee path blocked, recalculating.")
fleeState.Exit() -- Clean up old path
fleeState.Enter() -- Re-run the enter logic to find a new path
end
end,
Update = function(dt)
-- Most logic is event-driven, but you could add checks here
-- For example, check if health has recovered.
end,
Exit = function()
-- Disconnect all events to prevent memory leaks
for _, connection in pairs(fleeState.connections) do
connection:Disconnect()
end
table.clear(fleeState.connections)
-- Stop any movement
humanoid:MoveTo(npc.PrimaryPart.Position)
print("NPC is leaving Flee state.")
end,
}
This state does three key things: finds the closest safe point, calculates a path, and moves the NPC along that path using the same MoveToFinished event pattern from your Patrol state.
4. Making the Escape "Smarter"
Currently, PathfindingService will find the shortest path to the safe point. But what if the shortest path leads the NPC right past the player who is trying to attack it? A smarter NPC would prefer a safer, more discreet route, even if it's longer.
We can achieve this by using pathfinding costs. By assigning higher costs to "dangerous" areas, we can tell the PathfindingService to avoid them.
The following video and documentation explain this concept clearly.
Pathfinding (AI, Dynamic Paths, Modifiers) - Roblox Advanced Scripting #35 (2025)
This video from BrawlDev shows how to use material costs and Pathfinding Modifiers to influence an NPC's path. Pay close attention to how it makes the NPC prefer one path over another.
Watch from 16:43 to 19:53 to understand how setting costs for materials (like Wood vs. Neon) can create a 'smarter' path. Then, watch from 21:16 to 23:36 to see how a PathfindingModifier object can create a 'danger zone' that the NPC will avoid.
Let's apply this. We'll create a special PathfindingModifier to label a "Danger Zone" around the player. When the NPC flees, it will try to avoid this zone.
Exercise: Implement a Dynamic Danger Zone
-
Create the Modifier Part: Create a
Partand name itDangerZone. Parent it to your NPC model. This part will move with the NPC and represent the area it considers dangerous (i.e., the area around itself).- Set
Anchoredto false. - Set
CanCollideto false. - Set
Transparencyto 0.8. - Set
Sizeto something large, like50, 10, 50. This is the radius of danger. - Weld this part to the NPC's
HumanoidRootPartso it moves with the NPC.
- Set
-
Add the PathfindingModifier: Inside the
DangerZonepart, insert aPathfindingModifierobject. In its properties, set theLabeltoDanger. -
Update the Path Creation: We need to tell the
PathfindingServiceabout this new cost. We'll modify how we create the path in thefleeState.Find this line in
fleeState.Enter:fleeState.path = PathfindingService:CreatePath()Replace it with this:
-- Define the agent parameters with costs local agentParams = { Costs = { -- Any area with the 'Danger' label is extremely undesirable Danger = 50 } } fleeState.path = PathfindingService:CreatePath(agentParams)
Now, when ComputeAsync is called for the flee path, the pathfinding algorithm will see a high cost associated with the area immediately around the NPC and will strongly prefer routes that go around this zone. This encourages it to find a path away from its current dangerous location, rather than just the shortest path to safety.
For more details on PathfindingModifier and Costs, the official documentation is an excellent resource.
Character pathfinding | Documentation - Roblox Creator Hub
The official Roblox documentation provides a detailed look at how to use PathfindingModifier and the Costs table to create intelligent pathing.
Read the section titled Pathfinding modifiers. It covers both setting costs by material (Set material costs) and by defined regions using a PathfindingModifier object (Work with regions), which is exactly what we just implemented.
Conclusion
Fantastic work! You have now implemented a Flee state, giving your NPC a crucial self-preservation instinct. It not only recognizes danger but also uses advanced pathfinding techniques to find a safer escape route.
Key Takeaways:
- Event-Driven Transitions: Using events like
Humanoid.HealthChangedis a powerful and efficient way to trigger state changes from anywhere in your FSM. - Defining Goals: The
Fleestate's effectiveness depends on having well-defined goals (our "Safe Points"). - Smarter Pathfinding with Costs: You can move beyond the "shortest path" and guide your NPC's movement by using
PathfindingModifierandCoststo define desirable and undesirable areas.
You have now built a very capable NPC AI with a range of behaviors. In the next module, we will switch gears and see how these same FSM principles can be applied to a completely different problem: managing UI Flow.