SimplePath - Pathfinding Module

I just tested the NPC again today and it works completely fine again, I don’t know why it was bugged yesterday causing the crash, strange…

Hello! I’m lazy but at the beginning of the topic you left a guide on how to implement this module, but I saw that the module has several updates ; Should I use the guide you left at the beginning? I really need to implement it in my game.

How does this module handle multiple NPCs running at once? Haven’t tried it out yet but I plan to make a wave based enemy system with a multiple enemies (5 - 25) and I was curious how this module will hold up when it comes to performance.

If you really need to implement it, don’t be lazy. Read the documentation :smiley:

Im having problems with multiple enemies using pathfinding, they’re moving towards the closest player but they keep walking back and then move a little towards the player and repeating. I’m writing it exactly like the documentation I don’t understand why its doing this. Btw it only does this when multiple enemies are spawned in but if there is only one it works perfectly.

--Import the module so you can start using it
local ServerStorage = game:GetService("ServerStorage")
local SimplePath = require(ServerStorage.SimplePath)

--Define npc
local dummy = script.Parent

--Create a new Path using the Dummy
local Path = SimplePath.new(dummy)

--Helps to visualize the path
Path.Visualize = false

local isDestroyed = false

local maxDistance = 100000

function getClosestPlayer()
	local nearestPlayer, nearestDistance
	for _, player in pairs(game.Players:GetPlayers()) do
		local character = player.Character
		local distance = player:DistanceFromCharacter(dummy.HumanoidRootPart.Position)
		if not character or 
			distance > maxDistance or
			(nearestDistance and distance >= nearestDistance)
		then
			continue
		end
		nearestDistance = distance
		nearestPlayer = player
	end
	if nearestPlayer.Character.HumanoidRootPart then
		return nearestPlayer.Character.HumanoidRootPart
	end
	
end

local run = script:WaitForChild("run")
local humanoid = dummy:WaitForChild("Humanoid")
local runAnimation = humanoid:LoadAnimation(run)


while true do
Path:Run(getClosestPlayer())
end
1 Like

Having an issue where when the Humanoid dies, the rig has a chance to disappear. I have added a ragdoll on death but when removing it still does the same thing.

Has worked well for my game with bot waves, but your surrounding logic needs to minimize how often it tries to recalc routes if things get stuck, etc

Edit: I should note that I manage all the npc from a single script and run their individual logic on a coroutine thread

I manage it from a single script as well. Do you mind sharing how I could optimize recalculating paths when it comes to enemies in waves, as that’s what I’m going for as well?

Keep in my mind, the NPC’s only disappear rarely when normally walking, but almost 80% of the time on death. Not sure if this has to do anything with recalc routes but I’m fairly new to this module.


function class:Start()
	local rig : Model = self.Rig
	local humanoid : Humanoid = self.Rig.Humanoid
	local radius = 20
	local player = nil
	local isFollowing = false
	local followRange = 80 -- Adjust the follow range as needed
	local nearestPlayer

	local Path = simplePath.new(rig)

	Path.Visualize = true

	self.Walk:Play()
	
	humanoid.StateChanged:Connect(function(old, new)
		if new == Enum.HumanoidStateType.Dead then
			self.Idle:Stop()
			self.Walk:Stop()
			Path:Destroy()
		end
	end)
	
	local function followPlayer()
		if rig.Humanoid.Health > 0 then
			nearestPlayer = findNearestPlayer(followRange, rig.PrimaryPart)

			if nearestPlayer then
				isFollowing = true

				--Cancel Current Path
				if Path.StatusType.Active == true then
					Path:Stop()
				end

				Path:Run(nearestPlayer.PrimaryPart.Position)
			else
				isFollowing = false
			end
		end
	end
	
	local function Wander()
		--Cancel Current Path
		if Path.StatusType.Active == true then
			Path:Stop()
		end
		
		self.Walk:Stop()
		self.Idle:Play()
		wait(math.random(2, 5))
		self.Idle:Stop()
		self.Walk:Play()
		Path:Run(rig.PrimaryPart.Position + Vector3.new(math.random(-radius, radius), 0 , math.random(-radius, radius)))
	end

	-- Check for nearby players periodically
	task.spawn(function()
		while true do
			if not isFollowing then
				followPlayer()
			end
			wait() \
		end
	end)

	-- Wandering
	Path.Reached:Connect(function()
		if not isFollowing then
			Wander()
		else
			if not self.Walk.IsPlaying then
				self.Idle:Stop()
				self.Walk:Play()
			end
			
			Path:Run(nearestPlayer.PrimaryPart.Position)
		end
	end)
	
	Path.WaypointReached:Connect(function()
		if isFollowing then
			if not self.Walk.IsPlaying then
				self.Idle:Stop()
				self.Walk:Play()
			end
			
			Path:Run(nearestPlayer.PrimaryPart.Position)
		end
	end)
	
	Path.Error:Connect(function(errorType)
	--	warn(errorType)
		if isFollowing then
			if not self.Walk.IsPlaying then
				self.Idle:Stop()
				self.Walk:Play()
			end
			
			Path:Run(nearestPlayer.PrimaryPart.Position)
		else
			Wander()
		end
	end)
	
	--Dummy knows to compute path again if something blocks the path
	Path.Blocked:Connect(function()
		if isFollowing then
			if not self.Walk.IsPlaying then
				self.Idle:Stop()
				self.Walk:Play()
			end
			
			Path:Run(nearestPlayer.PrimaryPart.Position)
		else
			if Path.StatusType.Active == true then
				Path:Stop()
			end	
			
			Path:Run(rig.PrimaryPart.Position + Vector3.new(math.random(-radius, radius), 0, math.random(-radius, radius)))
		end
	end)

	-- Start wandering initially
	Path:Run(rig.PrimaryPart.Position + Vector3.new(math.random(-radius, radius), 0, math.random(-radius, radius)))
end

Here is my currently enemy code, not sure how I’m able to fully optimize it but help would be apprecitaed

2 Likes

please help, wth do i do when this happens?
ServerStorage.SimplePath:327: attempt to perform arithmetic (sub) on number and nil

Same here. I set the NPC’s WalkSpeed to 18 and it causes the NPC to stutter which makes it slower. Have you found a solution yet?

I dont know why, but after Path:Destroy() (i think), its still gives an error like:

attempt to index nil with 'Status'
1 Like

Also experiencing this issue. It seems to occur once you’ve destroyed a path once and now want to create a new one.

When I have multiple npcs, and I use Path:Destroy(), it says attempt to call missing method Destroy() on table

Really good module! Easy to use, and working perfectly!

I don’t know how this module does it, but it was an absolute lifesaver with being able to cancel previous paths

i love you glorious grayzcale :star_struck:

does simplepath have a timeout when getting stuck?

Why does my client side code does not work? Its in starterplayerscripts, it should work

local SimplePath = require(game.ReplicatedStorage:WaitForChild("SimplePath"))

local events = {
    Create = game.ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("CreatePath");
    Start = game.ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("StartPath");
    Stop = game.ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("StopPath");
    Destroy = game.ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("DestroyPath");
}
local paths = {}

local function Create(Agent)
    if Agent:IsA("Model") and Agent:FindFirstChild("Humanoid") and Agent:FindFirstChild("HumanoidRootPart") and not paths[Agent] then
        paths[Agent] = SimplePath.new(Agent)
    end
    return paths[Agent] --can be ignored for sure
end
local function Start(Agent, Destination) --specific agent to pathfind
    local path = paths[Agent]
    if path then
        print("RUNNING") --it prints but agent doesnt pathfinds?
        path:Run(Agent.HumanoidRootPart.Position + Vector3.new(math.random(15, 20), 0, math.random(15, 20)))
    end
end
local function Stop(Agent) --specific agent to stop
    print("stop called")
    if paths[Agent] then
        if not SimplePath.StatusType.Idle then
            paths[Agent]:Stop()
        end
    end
end
local function Destroy(Agent) --destroy Path (used when agent is dead) 
    print("destroy called")
    paths[Agent]:Destroy()
    paths[Agent] = nil
end

events.Create.OnClientEvent:Connect(Create)
events.Start.OnClientEvent:Connect(Start)
events.Stop.OnClientEvent:Connect(Stop)
events.Destroy.OnClientEvent:Connect(Destroy)

I wonder what makes simple path switching from pathfinding routes so quickly without much delay

I believe it does, but take quite a while

1 Like