ProPath - Pathfinding Module

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

4 Likes

added custom colors for each npc if u want to add that so now

your default settings are

local defaultSettings = {
	TimeVariance = 0.08, -- minimum seconds between Run() calls, increase if you're repathing very frequently
	ComparisonChecks = 2, -- how many checks before declaring stuck, higher = less sensitive
	JumpWhenStuck = true, -- attempts to jump out if the npc stops moving, won't always work on tight geometry
	WaypointReachDistance = 3, -- how close the NPC needs to be to count a waypoint as reached
	RepathWhenBlocked = true, -- automatically calls Run() again when the path gets blocked
	VisualParentName = "ProPathWaypoints", -- folder name in workspace that holds the debug parts
	WaypointColor = Color3.fromRGB(255, 139, 0), -- normal walking waypoint color
	JumpWaypointColor = Color3.fromRGB(255, 60, 60), -- jump color, yada yada
	GoalWaypointColor = Color3.fromRGB(0, 255, 90),
}```

but if u wanna change them, then you an


Example:

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
WaypointColor = Color3.fromRGB(155, 88, 255),
JumpWaypointColor = Color3.fromRGB(34, 255, 86),
GoalWaypointColor = Color3.fromRGB(0, 0, 0),
})

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)

6 Likes

is there a way to edit the goalwaypoint to be somewhere else instead of inside the target? i’ve been trying to get this work with some edits on my own to make the agent go 2 studs behind of the players z position to no avail (this either has a really simple answer or is complicated to do)