NoobPath | Easy Pathfinding

NoobPath is a Pathfinding module script with Auto Timeout, Non-humanoid support and Waypoint adjustments!

Get it here: NoobPath - Creator Store
Version 1.5

Inspired by SimplePath by Grayzcale :+1:
Most Signals/Methods Interchangeable With SimplePath :slight_smile:

I suggest checking out SimplePath first :heart:

GoodSignal by stravant is used in this module :star_struck:

NoobPath vs. SimplePath:



Similar Performance using loop


All the example code assume you have a script inside a Humanoid Rig

local NoobPath = require(PathToNoobPath)

local Guy, JumpDetectConnection = NoobPath.Humanoid(script.Parent, {WaypointSpacing = 10})
-- Parameter 1 is a Character Model, Parameter 2 is AgentParams(Basically settings for pathfinding)

Check Here for Pathfinding basics


Signal Implementation & Partial Demonstration Example:

local NoobPath = require(PathToNoobPath)

local Guy, JumpDetectConnection = NoobPath.Humanoid(script.Parent, {WaypointSpacing = 10, PathSettings = {SupportPartialPath = true})

if Guy.Idle then -- Access Character state through .Idle
    print("Idle")
end

Guy.Reached:Connect(function(Waypoint, IsPartial)
	print("Reached: " .. tostring(Waypoint) .. " | Partial: " .. tostring(IsPartial))
	task.wait(0.1)
	Guy:Run()
end)

Guy.WaypointReached:Connect(function(Waypoint, NextWaypoint)
	print("WaypointReached: " .. tostring(Waypoint) .. " | NextWaypoint: " .. tostring(NextWaypoint))
	Guy:Run()
end)

Guy.Error:Connect(function(ErrorType : string)
	print("Error: ".. ErrorType)
	task.wait(0.1)
	Guy:Run()
end)

Guy.Trapped:Connect(function(TrapType : string)
	print("Trapped: ".. TrapType) 
	if script.Parent.Humanoid:GetState() ~= Enum.HumanoidStateType.Climbing then -- Climbing is slower than usual
		Guy.Jump() -- Jump to unstuck
	end
	
	Guy:Run()
end)

Guy.Timeout = true -- Off by default, it calculate time necessary to travel between Waypoints and Check if the Character arrived after that amount of time, Trapped will be fired if the Character didn't reach Waypoint in time
Guy.Speed = 16 -- 16 by default, used to allow Timeout calculate time necessary to travel between Waypoints
Guy.Visualize = true -- Off by default, basically generate visual spheres to represent Waypoints

Guy:Run(workspace.SpawnLocation) -- If a Location is given to :Run(), it will be stored and become the default Location to go if there's no argument given
-- Location : Vector3 | BasePart | Model

Loop Implementation Example:

local NoobPath = require(PathToNoobPath)
local Guy,JumpDetectConnection = NoobPath.Humanoid(script.Parent, {WaypointSpacing = 10})

Guy.Visualize = true

while true do
	task.wait()
	Guy:Run(workspace.SpawnLocation)
end

Non-humanoid Example:

local NoobPath = require(PathToNoobPath)

-- NoobPath.Humanoid() pass in those functions & signals with humanoid defaults
-- There's no JumpDetectConnection as you have to set things up manually
local Guy = NoobPath.new(
	script.Parent,
	{WaypointSpacing = 10},
	function(PositionToGo : Vector3)
		-- custom logic to move the Character to PositionToGo
	end,
	function()
		-- custom logic to make the Character Jump
	end,
	JumpFinishedSignal, -- place a signal here to indicate Finished Jumping, it can be Roblox Signal, BindableEvent.Event, or just use the GoodSignal inside the module
	MoveFinishedSignal -- place a signal here to indicate Finished Moving, it can be Roblox Signal, BindableEvent.Event, or just use the GoodSignal inside the module
)

-- Remember to Destroy when not using
local NoobPath = require(PathToNoobPath)
local Guy, JumpDetectConnection = NoobPath.Humanoid(script.Parent, {WaypointSpacing = 10})
-- Do Stuff
Guy:Stop()
-- Do Stuff
JumpDetectConnection:Disconnect()
Guy:Destroy()

Methods & Signals & Variables not demonstrated in the examples are Private

Please give me feedback and suggestions, thank you

30 Likes

What differentiates this from for example SimplePath that you mentioned and other pathfinding systems?

1 Like

maybe the first sentence :person_shrugging:
“NoobPath is a Pathfinding module script with Auto Timeout, Non-humanoid support and Waypoint adjustments!”

3 Likes

:man_facepalming: I asked too quickly, thank you for the clarification

2 Likes

lets goo this module works better than simplepath!

Also, u made this post when i wanted to find a pathfinding module lol

1 Like

Does this work out of the box with the recent pathfinding update (e.g. partial paths)?

Yes, new pathfinding features works with the module. If a partial path got generated, it will be treated just like a regular path, I just updated the module to fire reached with an additional IsPartial boolean parameter. However, right now partial path is inconsistent, since pathfinding service sometimes return either partial path or no path under similar conditions, triggering and switching between error and reached signal. But enabling partial path does give you a higher chance to reach or at least get close to the destination if no path was found. Other new features works with the module.

2 Likes

What’s best practice if I wanna use it for something that needs to be looped over and over for example

function weepingAngelModule:update()
	local isSeen = false
	local closestPlayerDistance = 1000
	local closestPlayerPosition = nil
	local stopExecuted = self.stopExecuted or false  -- Track whether stop logic has been executed

	for i, player in pairs(weepingAngelModule.players) do
		if player[2] == nil then
			continue
		end
		local character = player[1].Character
		local humanoid = character:FindFirstChild('Humanoid')
		local humanoidRootPart = character:FindFirstChild('HumanoidRootPart')

		if humanoidRootPart == nil or humanoid == nil then
			continue
		end
		if humanoid.Health == nil then
			continue
		end

		fauxCamera.CFrame = player[2]
		fauxCamera.DiagonalFieldOfView = player[3][1] * 2
		fauxCamera.FieldOfView = player[3][2] / 2
		fauxCamera.MaxAxisFieldOfView = player[3][3] / 1
		local position, isOnScreen = fauxCamera:WorldToViewportPoint(self:getPosition())
		if isOnScreen then
			isSeen = true
			break
		end
		local plrPosition = humanoidRootPart.CFrame.Position
		local playerDistance = (self:getPosition() - plrPosition).Magnitude

		if playerDistance < closestPlayerDistance then
			closestPlayerDistance = playerDistance
			closestPlayerPosition = humanoidRootPart.CFrame.Position
		end
	end

	-- Handle movement based on visibility state
	local model, jConnection = noobPath.Humanoid(self.model, {WaypointSpacing = 10, AgentCanJump = false})
	model.Speed = 12
	model.Visualize = true
	
	if isSeen == false and closestPlayerPosition  then
		self.isChasing = true
		print('test?')
		if self.stopExecuted then
			self.stopExecuted = false  -- Reset stop logic flag
		end
	
		self.humanoid.WalkSpeed = 12
		self.moveToPosition = closestPlayerPosition
		model:Run(self.moveToPosition)
	else
		if stopExecuted == false then
			self.stopExecuted = true
			self.moveToPosition = self:getPosition()
			self.humanoid.WalkSpeed = 0
			jConnection:Disconnect()
			model:Stop()
			model:Destroy()
		
		end
	end

end

What would you recommend for the best performance

Since the update is a method, I assume you have a constructor, right now you keep creating the NoobPath object & Jumping connection and destroying them if they aren’t needed. Which might be better if you can directly setup the NoobPath & Connection in the constructor, so there’s only 1 thing to be destroyed when no longer needed. Also I think you can put self:getPosiiton() into a local variable, so you don’t need to call the method from the self table everytime. And perhaps break down the code into different methods (ex: StopExecute(), KeepChasing(), GetTarget(), etc) to make the code more organized. In addition, I personally suggest number loops instead of in pairs unless weepingAngelModule.players isn’t an array. And you probably don’t need the stopExecuted variable since you are getting it from the self table anyways to ensure dynamic accuracy.

Version 1.3

Precise option
Interval option
Improved .Humanoid()'s Jump function

there’s this weird glitch where if the npc chases someone for a few seconds, the npc will stop moving
npc idle.rbxl (146.5 KB)

1 Like

I am dumb, accidentally used “NoobPath.new” in a spot where I should’ve used “NoobPath.Humanoid” lmao

The reason why is because when NoobPath get the monster’s position, it get its primarypart’s position, the rootpart in this case. However, position doesn’t take consideration of the pivot offset. Although the rootpart’s pivot is on the ground, the rootpart’s position is high up in the air due to the monster’s height, while the player character, a normal rig have their rootpart position only a few studs above the ground. Making it impossible to reach.

local function getNearestTarget(npc, root)
	local dist = 1000
	local char

	
		for _, player in pairs(game.Players:GetPlayers()) do
			local vChar = player.Character
			if vChar and vChar:FindFirstChild("Humanoid") and vChar:FindFirstChild("HumanoidRootPart") then
				local vHum = vChar:WaitForChild("Humanoid")
				local vRoot = vChar:WaitForChild("HumanoidRootPart")
				if vHum.Health > 0 then
					local magnitude = (vRoot.Position - root.Position).Magnitude
					if magnitude <= dist then
						dist = magnitude
						char = vChar
					end
				end
			end
		end
	return char
end

local npc = script.Parent

local ServerStorage = game:GetService("ServerStorage")
local NoobPath = require(ServerStorage.NoobPath)
-- you don't need to require Path, NoobPath is already using it

-- Define AgentParams for the NPC
local AgentParams = {
	AgentRadius = 2,  -- Example value
	AgentHeight = 2,  -- Example value
	AgentCanJump = false,
--	AgentJumpHeight = 0, -- Roblox didn't implement this option, it does nothing
--	AgentMaxSlope = 45, -- Roblox didn't implement this option, it does nothing
	Costs = {warning = 1}
}

-- Initialize NoobPath with AgentParams
local Guy, JumpDetectConnection = NoobPath.Humanoid(npc, AgentParams)

local designatedAreasFolder = workspace:WaitForChild("ai_zones")
local designatedParts = {}

-- Collect designated area parts
for _, part in ipairs(designatedAreasFolder:GetChildren()) do
	if part:IsA("BasePart") then
		table.insert(designatedParts, part)
	end
end

-- Function to check if NPC is inside any designated area part
local function isInsideAnyPart(character)
	for _, part in ipairs(designatedParts) do
		local partPos = part.Position
		local partSize = part.Size
		local characterPos = character.HumanoidRootPart.Position

		-- Check if the NPC is within the area of the part
		local xRange = math.abs(characterPos.X - partPos.X) <= partSize.X / 2
		local yRange = math.abs(characterPos.Y - partPos.Y) <= partSize.Y / 2
		local zRange = math.abs(characterPos.Z - partPos.Z) <= partSize.Z / 2

		if xRange and yRange and zRange then
			return true
		end
	end
	return false
end

Guy.Visualize = true
JumpDetectConnection:Disconnect()

-- runservice.stepped/heartbeat/renderstepped is pretty much the same as a loop, don't worry about the change
-- you don't need connections as you just repeat :Run() at a certain interval(ex: 0.1)
-- which is frequent enough
while task.wait(0.1) do
	local target = getNearestTarget(npc, npc.HumanoidRootPart)

	if target then
		local targetRoot = target:WaitForChild("HumanoidRootPart")

		-- Check if NPC is inside any designated area
		if isInsideAnyPart(npc) then
			-- Run towards target within designated areas
			
			-- since you are not checking for trapped, (Timeout != true), there's no need to Jump to unstuck
			-- also idle is just a state, by calling :Run(), idle automatically change to false
			
			Guy:Run(targetRoot)
			print("Ran")

		else
			-- Move directly to the target outside designated areas
			if not Guy.Idle then
				Guy:Stop()
			end
			

			npc.Hunt:MoveTo(targetRoot.Position)
			print("Stopped")
		end

	end
end

I changed ur code a little bit
Version 1.4 will use PivotPosition instead

it still doesnt work, when you touch a wall and go around it, there is a chance where the npc will stop making paths and stop moving

I wish this module would exist earlier when I was making my own Pathfinding NPC using SimplePath. There’s a lot of things that it does well and also not. I’ve already modified a lot of SimplePath source code to fix those shortcomings.

Would you mind getting into detail about how NoobPath is different from SimplePath?

Since I believe that SimplePath already supports non-humanoid and other things similar.

Thank you for making SimplePath better, nonetheless.

Lastly, it would be amazing for this module to support some additional features of the new pathfinding update!

could you please send the new place, so I can test what’s going wrong, or tell me what changes did you make since the previous place you sent. Make sure you used Version 1.4

1 Like

NoobPath have timeout feature, based on the speed(stud/s) of the agent and the distance between the waypoints, it will automatically calculate time required to travel between waypoints. If after that amount of time and the agent still didn’t reach the next waypoint, the Trapped signal will be fired, indicating that the agent is stuck. The Trapped signal also take in consideration of Humanoid:MoveTo() expiring after a certain threshold, firing if it was expired. Compared to SimplePath, it’s way easier to setup custom non-humanoid npcs in NoobPath, all you need is to put the desired functions and signals into the .new() constructor, can use the NoobPath object regularly. NoobPath also adjust waypoints by removing waypoints that are too close to each other, reducing the risk of getting stuck when trying to jump over an obstacle, which happens often in SimplePath. Other than that, it have some minor improvements in terms of delaying pathfinding while the character is in air (simplepath also does this, but more robust). I also added partial path into NoobPath, and there’s another feature called Precise, which prevent the character from continuing to the next waypoint while in air, slower movements but more accuracy for obby. It’s also more neat, with Path state represented by .Idle, a simple boolean. All methods also get autosuggested. NoobPath is basically SimplePath but more convenient, and the performance is only slightly more expensive.

Also if you found things that don’t work well, please give me suggestions.

1 Like

it is the same file i gave before, to do the glitch, just go around the wall and go behind the npc and it will bug the pathfinding

1 Like

I fixed that in 1.5 (forgot to add the PivotPosition in :Run())

1 Like

Version 1.5

Removed:

Interval: No longer needed due to optimization of the Jump & WaitUntilLanded
Precise: Not great for performance in general, not very useful

New:

fixed a problem with Server’s NetworkOwnership not working on Path Npc sometimes
optimized Jump & WaitUntilLanded
fixed PivotPosition not being used in :Run()

1 Like