ProPath is a pathfinding module that allows you to quickly pathfind, and make a script for npcs and such. This module will save you a lot of time and effort.
ProPath handles edge-case scenarios, exposes events for completion, blocking, and stuck detection and it handles errors for you as well.
Get it here: https://create.roblox.com/store/asset/138247578169248/ProPath-Pathfinding-Module
Usage Example:
NPC walking to a target
local ProPath = require(game.ServerScriptService.ProPath)
local npc = workspace.Rig5
local goal = workspace.Target
local path = ProPath.new(npc, {
AgentRadius = 2,
AgentHeight = 5,
AgentCanJump = false,
}, {
WaypointReachDistance = 4,
RepathWhenBlocked = true, -- handles Blocked automatically, no need to manually repath
})
path.Visualize = true
-- Fires once when the NPC fully arrives — good place to loop or do something next
path.Reached:Connect(function(agent, waypoint)
print(agent.Name, "reached the target")
-- if you want it to loop back and run again, do it here
-- task.wait(1)
--path:Run(goal)
end)
path.Blocked:Connect(function(agent, waypoint)
print(agent.Name, "got blocked, repathing...")
-- no path:Run() needed here cuz RepathWhenBlocked handles it
end)
-- Only repath on specific errors worth retrying
path.Error:Connect(function(errorType)
warn("Pathing error:", errorType)
-- LimitReached just means Run() was called too fast
if errorType == ProPath.ErrorType.LimitReached then return end
-- For errors, wait a moment then retry
task.wait(1)
path:Run(goal)
end)
path.WaypointReached:Connect(function(agent, prev, next)
path:Run(goal) -- if you want it to repath when the goal moves
end)
path:Run(goal)
Constructor
ProPath.new(agent: Model, agentParameters: dictionary or nil settings: dictionary or nil)
Properties
Property - Type - Default - Description
Status: StatusType - READ ONLY "Idle"
"Current state: Idle or Active"
LastError: ErrorType - READ ONLY nil
"Most recent error fired on this path"
Visualize: boolean - false
"Spawns colored debug spheres at each waypoint — orange = normal, red = jump, green = goal"
Speed: number - nil
"Overrides Humanoid.WalkSpeed on every move call. Leave nil to use the humanoid's current speed"
Methods
Path:Run(target: Vector3 | BasePart | Model | nil) -> boolean
Computes a path to target and starts walking it. If target is omitted, ProPath re-uses the last target passed to Run() (useful for repathing).
Returns false and fires Error instead of starting a path if:
* no target is available (MissingTarget)
* Run() is called again before TimeVariance seconds have passed since the last call (LimitReached) — this check returns immediately rather than blocking the calling thread
* ComputeAsync fails, returns NoPath, or returns fewer than 2 waypoints (ComputationError)
Path:Stop()
Halts the agent in place, clears visuals, sets Status to Idle, and fires Stopped. Safe to call on an already-idle path (no-op).
Path:Destroy()
Stops the path, disconnects all internal connections, destroys the underlying Path object and all event objects, and clears the table. Call this when the NPC dies or is removed.
Events
All events are exposed as properties (e.g. path.Reached:Connect(...)).
Reached(agent, waypoint)
"Fires when the agent reaches the final waypoint"
WaypointReached(agent, previousWaypoint, nextWaypoint)
"Fires when the agent advances from one waypoint to the next"
Blocked(agent, waypoint)
"Fires when an obstruction is detected at or ahead of the current waypoint. If RepathWhenBlocked is enabled, ProPath automatically repaths right after this fires"
Error(errorType)
"Fires when any error condition occurs"
Stopped(agent)
"Fires when Path:Stop() is called on an active path"
Settings
Setting - Type - Default - Description
TimeVariance: number - 0.08
"Minimum seconds required between Run() calls; calls inside this window immediately fail with LimitReached"
ComparisonChecks: number - 2
"Consecutive stuck detections before declaring AgentStuck"
JumpWhenStuck: boolean - true
"Attempts a jump when stuck is detected"
WaypointReachDistance: number - 3
"If MoveToFinished reports failure but the agent is within this many studs of the waypoint, ProPath treats it as reached anyway"
RepathWhenBlocked: boolean - true
"Automatically recomputes and resumes the path toward the stored target when Blocked fires"
VisualParentName: string - "ProPathWaypoints"
"Name of the Workspace folder used to hold debug waypoint parts when Visualize is on"
Enums
ProPath.StatusType = { Idle = "Idle", Active = "Active" }
ProPath.ErrorType = {
LimitReached = "LimitReached",
TargetUnreachable = "TargetUnreachable",
ComputationError = "ComputationError",
AgentStuck = "AgentStuck",
MissingTarget = "MissingTarget",
Error Types and their causes
LimitReached
"Run() called too soon after the previous call"
TargetUnreachable
"A waypoint was not reached and the agent wasn't close enough per WaypointReachDistance; the path is aborted"
ComputationError
"ComputeAsync failed, found no path, or returned fewer than 2 waypoints"
AgentStuck
"Position hasn't changed meaningfully across stuck checks"
MissingTarget
"Run() called with no target and none stored from a previous call"
Static Methods
ProPath.GetNearestCharacter(fromPosition: Vector3) -> (Model?, number)
Returns the nearest living player character (Humanoid.Health > 0) to fromPosition and its distance, or nil if no eligible character exists.
NOTE: This doesn’t support non humanoid agents so dont use it for non humanoid stuff
ENJOY ProPath