SimplePath - Pathfinding Module

I’m running into behavior with this module. Here’s how I run it.

         if ClosestObject then
			local Combat_NpcStates = CharacterModel:WaitForChild("Combat_NpcStates")
			--[Trigger States]
			local TriggerStates = Combat_NpcStates:WaitForChild("TriggerStates")
			local Aggro = TriggerStates:WaitForChild("Aggro")
			
			local RandomX = Universal_Checks.TrueRandom(-ClosestObject.Size.X/2, ClosestObject.Size.X/2)
			local RandomZ = Universal_Checks.TrueRandom(-ClosestObject.Size.Z/2, ClosestObject.Size.Z/2)
			
			local PathfindPosition = (ClosestObject.CFrame * CFrame.new(RandomX, 0, RandomZ)).Position
			
			local CalculatedPath = Pathfind_Module.new(CharacterModel, {AgentHeight = 5, AgentRadius = 3, AgentCanJump = true,})
			CalculatedPath:Run(PathfindPosition)
			Connections["PathCompleteConnect"] = CalculatedPath.Completed:connect(function(Status)
				CalculatedPath:Stop()
				CalculatedPath:Destroy()
				--check distance of all players to aggro
			end)
			
			Connections["AggroChangeConnect"] = Aggro:GetPropertyChangedSignal("Value"):connect(function()
				if Aggro.Value ~= nil then
					CalculatedPath:Stop()
					CalculatedPath:Destroy()
				end
			end)
		end

Here’s what happens. This causes a HUGE lag spike. Do you notice anything wrong in my code that could cause this?


EDIT: Turns out I was using the old version of the module. I believe the issue is fixed!

So, I have been using this module for a while. I change absolutely nothing and suddenly it stops working. What happens is the NPC doesn’t move. Here is my code:

task.spawn(function()

                    while true do

                        task.wait(0.1)

                        path:SetHipHeight(1)

                        path:Run(character.PrimaryPart)

                    end

                end)

I opened an issue on github, I would like it if you checked it out.

Its probably because of the time out added in the module if you updated to a newer version so by commenting

coroutine.wrap(function()
	while self._active do
		if self._elapsed and tick() - self._elapsed > 1 then
			Timeout(self); break
		end
		RunService.Stepped:Wait()
	end
end)()

It should fix the problem

I don’t think it is. I make my own pathfinding instead and my npc still is broken.

Then that means a part in the model is anchored

Yet nothing is anchored. The entire model is unanchored.

Maybe for some reason there is an object preventing the path being calculated.

This loop is necessary in order to prevent the possibility of infinite yielding in the case where the humanoid/model doesn’t reach the next waypoint.

It might be a glitch with Roblox. The NPC is floating above the ground like it has a bigger hitbox, yet thats no the case.

Uhh why is my AI stuttering?heres da script

wait(5)
local SimplePath = require(game.ServerScriptService.SimplePath)
local closestplrdis = nil
local plrHead = nil
while true do
	for _, v in pairs(game.Players:GetChildren()) do
		if closestplrdis == nil then
			if v.Character:FindFirstChild("Head") then
				closestplrdis = (script.Parent.Head.Position - v.Character.Head.Position).Magnitude
				plrHead = v.Character.Head
			end
		else
			local plrdistance = (script.Parent.Head.Position - v.Character.Head.Position).Magnitude
			if plrdistance < closestplrdis then
				closestplrdis = plrdistance
				plrHead = v.Character.Head
			end
		end
	end
	local Path = SimplePath.new(script.Parent)
	Path:Run(plrHead)
	wait()
end
1 Like

Does the path auto delete when Path.Reached is fired?

	MAIN_PATH.Reached:Connect(function()
		if LowestOBJ then
			LowestOBJ -= 1
		end
		spawn(function()
			MAIN_PATH:Destory()
			BindBrain(Rig)			
		end)
		LowestOBJ = nil
	end)

Line errors when :Destory() is called, claiming that Main Path is nil.

SimplePath was rewritten along with complete documentation including in-depth examples.

2 Likes

Nice module.

Be awesome if you could have a variable that contains how many studs (0.1, 1, 1+) away from the goal the NPC should stop. You could modify the pathfinder return results last waypoint and deduct some distance from it? This would allow us to stop the NPC a measurable distance from the goal and do some animations (kick\punch). It’d also stop the crowding\jumping on top of the goal when the NPC arrives.

That’s a very good suggestion. But in my opinion, you can easily stop pathfinding when the NPC is a certain distance from the goal. Off the top of my head, one of the ways I could think of is by performing magnitude checks using the Path.WaypointReached event whenever the NPC reaches the next waypoint. I’m sure there are many other ways you can go about achieving this easily. It mostly depends on how your code is structured.

1 Like

Yes it’d be useful to allow for some range wouldn’t it.

I think WaypointReached would be too late, here’s a simplfied rendition of how I worked the idea into my adoption of your module code.

if Goal then

	if (script.Parent.PrimaryPart.Position - Goal.Position).Magnitude > 10 then

		Path:Run(Goal)				

	else

		if Path["_status"] == "Active" then -- So that we do not invoke stop more than once or it errors

			Path:Stop()

		end
	end	
end

Be cool to integrate this into the module script, I’m calling the Path:Run repeatedly so there’s an opportunity for the modulescript to detect distance and do a stop while the humanoid is moving and before it reaches the waypoint, then start ignoring Path:Run calls until distance isn’t restricting.

This would be cool:

local Settings = {
	TIME_VARIANCE = 0.07;
	COMPARISON_CHECKS = 1;
	**DISTANCE_TO_STOP = 10;** -- Magnitude difference
}

:slight_smile:

I will definitely consider adding the feature. Thanks for your suggestion.

1 Like

I modified your code, hope you don’t mind me showing examples and modding it like this, I need this built-in and configurable per-NPC.

The Path:Run function just needed a slight modification by adding DISTANCE_TO_STOP as a param being passed, and some code for it to all work

function Path:Run(target, DISTANCE_TO_STOP)
	
	-- NEW CODE

	if DISTANCE_TO_STOP then -- Check if magnitude at or below DISTANCE_TO_STOP

		if (target and self._agent and self._agent.PrimaryPart) and (self._agent.PrimaryPart.Position - target.Position).Magnitude <= DISTANCE_TO_STOP then 

			if self._status ~= Path.StatusType.Idle then

				self:Stop()

				return

			end
			
			return
				
		end
	end

	-- NEW CODE

Path:Run is invoked as usual, or you can add the extra param to enable the behaviour:

Path:Run(Goal, DISTANCE_TO_STOP)

I know I said I’ll consider adding this feature in. However, that was before I realized some important details. Allow me to explain. Now the most common method of doing something like would be to compare the magnitude of the vector between the NPC’s PrimaryPart position and the target position. If I implement this feature in the module, SimplePath wouldn’t account for the sizes of either the PrimaryPart or target and this can result in undesired behaviour by incorrect distance results.

The second option would be to perform checks on the waypoints instead of the actual NPC or target. This can prove to be a better solution, but there is a downside of using this method. Imagine a platform with another platform directly above it and both are connected by stairs. The NPC is on the lower platform and tries to navigate up the stairs to reach the next platform to its target. Now when the NPC is on the lower platform, and the script detects that the waypoint distance to the target is lower than a certain value, it’ll end up removing the waypoints on the lower platform because those waypoints are closer to the target. Furthermore, this problem also exists for the very first method.

The best immediate solution would be to give the user the opportunity to perform distance checks on their own which can prove to be a more precise method because the user is aware of the dimensions of the NPC and will do the checks accordingly. Alternatively, a possible theoretical solution to these problems would be to iterate over the waypoints and compare the distance by taking into account the waypoint’s unit vector pointing to the consecutive waypoint. Maybe I will implement something like this in the future. But for now, I think it’s best to let the user handle the distance checks on their own.

Thank you for your thought out reply.

I’m okay with this not being adopted, I stumbled upon your script and needed to fit it into my project in the most suitable way and posted the changes.

I like how it was written SimplePath, very clean and understandable code.

An obserable problem with PathFinding is that it puts the NPC on the goal, so NPC’s pile up. I wanted them to stop a short distance away from the goal, a magnitude of 5 or less, SimplePath now handles that for me while a movement script does checks for 5 or less and performs the animations needed for combat. Overal it works quite well. I have some funky behaviour from scaled up NPC’s that get snagged in non-complex scenery, I was going to go back later today when I begin coding and have a look at how your script is handling hipheight.

I also had to brace with additional error checking the ComparePosition function, here:

local function comparePosition(self)
	
	if self._currentWaypoint == #self._waypoints then return end

-- NEW CODE

	if self._agent and self._agent.PrimaryPart and self._position then -- Validation check added by me

-- NEW CODE

I’ll keep an eye on your updates and fit my changes in where needed, Thanks again!