SimplePath - Pathfinding Module

you try to find the target’s character every frame physics gets processed, however, the player’s character likely didn’t load by the time you run this script. Thus causing errors as you try to access humanoidrootpart when the char variable is nil(don’t exist).

local rs = game:GetService('RunService')
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local SimplePath = require(ReplicatedStorage.Modular_Scripts.SimplePath)
local animtrack = script.Parent.Humanoid:LoadAnimation(script.Parent.Grab)

local Debounce = {}
local zombie = script.Parent
local Path = SimplePath.new(zombie)
Path.Visualize = true

local AttackDistance = 2


rs.Heartbeat:Connect(function() -- Assign Charater Function
	char = SimplePath.GetNearestCharacter(script.Parent.HumanoidRootPart.Position)
end)

function GoTarget() -- Pathfinding Function
	task.wait(0.5)
    if char == nil then return end -- prevent the rest from executing if char don't exist
	if zombie.Humanoid.Health >= 1 then
-- unnecessary, only need to set once		Path.Visualize = true
		Path:Run(char.HumanoidRootPart)
	end
end

function ZombieHealthChanged() -- Zombie Health Detection
	if zombie.Humanoid.Health > 0 then
		-- Play Zombie Hurt Sound & Animation
		print("Zombie Is Hurt")
	else if zombie.Humanoid.Health == 0 then
			Path:Destroy()
			-- leaderstats.Cash.Value + 5
			task.wait(10)
			zombie:Destroy()
		end
	end
end


local function damageTarget() -- Hurt Player if zombie attacks
	local magnitude = (char.HumanoidRootPart.Position - zombie.HumanoidRootPart.Position).Magnitude
	if magnitude <= AttackDistance then
		print("Zombie is hurting player")
		char.Humanoid:TakeDamage(5)
	end
end

----Zombie knows to compute path again if something blocks the path
Path.Blocked:Connect(function()
	Path:Run(char.HumanoidRootPart)
end)

----If the position of Goal changes at the next waypoint, compute path again
Path.WaypointReached:Connect(function()
	Path:Run(char.HumanoidRootPart)
end)

----Zombie knows to compute path again if an error occurs
Path.Error:Connect(function(errorType)
	--Path:Run(char:GetPivot().Position)
	Path:Run(char.HumanoidRootPart)
end)

zombie.Humanoid.HealthChanged:Connect(ZombieHealthChanged)

---- Function Call
while true do
-- unnecessary, you already wait 0.5 inside GoTarget:   task.wait()
--    zombie.Humanoid.HealthChanged:Connect(ZombieHealthChanged)  you are creating a brand new connection that triggers whenever zombie health changed every 0.5 seconds, only a single connection is needed
	GoTarget()
    if char == nil then return end -- prevent the rest from executing if char don't exist
	
	damageTarget()
end

That code change caused the entire script to break instead of waiting until char was detected, I just put the zombie in server storage and spawn it after 15 seconds since it will be in a spawning system anyways.

can you change how tightly characters take corners this this?

have u tried adjusting the agentradius parameter ?

Hello developers, I am using the Simplepathfinding module and it did not exceed my expectations, any help will be useful

I am making a npc system{~sorry for the bad code~}
The problem is the npc always jumps!

Here is the Script:

local Players = game:GetService("Players")
local Runservice = game:GetService("RunService")

local SimplePath = require(game.ReplicatedStorage.Modules.SimplePath)

local self = script.Parent

local path = SimplePath.new(self)
path.Visualize = true

local NearestPlayer = nil
local Range = 30
local DistanceToStop = 5

local function findNearestPlayer()
	local playerList = Players:GetPlayers()
	
	local nearestPlayer = nil
	local distance = nil
	
	for _, player in pairs(playerList) do
		local character = player.Character or player.CharacterAdded:Wait()
		
		if character then
			local distanceVector = (self.HumanoidRootPart.Position - character.HumanoidRootPart.Position).Magnitude

			if not nearestPlayer then
				nearestPlayer = player
				distance = distanceVector
			elseif distanceVector < distance then
				nearestPlayer = player
				distance = distance
			end
		end
	end
	
	return nearestPlayer, distance
end

path.Error:Connect(function()
	if NearestPlayer then
		local character = NearestPlayer.Character or NearestPlayer.CharacterAdded:Wait()
		path:Run(character.HumanoidRootPart)
	end
end)

path.Blocked:Connect(function()
	if NearestPlayer then
		local character = NearestPlayer.Character or NearestPlayer.CharacterAdded:Wait()
		path:Run(character.HumanoidRootPart)
	end
end)

path.Reached:Connect(function()
	if NearestPlayer then
		local character = NearestPlayer.Character or NearestPlayer.CharacterAdded:Wait()
		path:Run(character.HumanoidRootPart)
	end
end)

Runservice.Heartbeat:Connect(function()
	local NearestPlayer, distance = findNearestPlayer()
	if NearestPlayer then
		if distance <= Range and distance >= DistanceToStop then
			
			path:Run(NearestPlayer.Character.HumanoidRootPart)
			
		else
			if path.Status == SimplePath.StatusType.Idle then
				return nil
			elseif path.Status == SimplePath.StatusType.Active then
				path:Stop()
			end
		end
	end
end)

Maybe because you are running it too frequently, heartbeat run 60 times a second, so you are generating 60 paths a second if a player is present. However, in theory it is same as while task.wait() do, which should work based on my experience. Another reason could be the signals kept getting triggered. Generally, if you are using signals, don’t use RunService connection or a while loop, just start it once and signals will handle the rest. And if you are using RunService connection or a while loop, no need to implement the signal connections.

Thank you, this will be useful!

using while task.wait() do causes the script to wait untill a path is made before finding another one
using Heartbeat wont do this causing the script to make another path before the last one is finished

That’s correct, I meant that they function the same(except waiting), and since the heartbeat in that script is on the bottom anyways, so it won’t matter.

what if there were different collision group?

Hi, from the info on the replies, do you have an updated script that performs better you can share?

Thanks

Here is the script, i’m not a all tops scripter. The code might not be optimized but here:

local Players = game:GetService("Players")

local SimplePath = require(ReplicatedStorage.Modules.SimplePath)

local self = script.Parent

local path = SimplePath.new(self, {
	AgentCanJump = true
})

-- for visualization

path.Visualize = true

-- range of npcs

local range = 30

-- stop/attack range for the npc

local attackrange = 5

-- cooldown for attack

local cooldown = 2

-- this is useful for debouncing the attack for the npc

local lastAttack = tick()

local isDead = false

-- this is for variation for animations, vfx to vary ig...

local  currentEffectType = 1

local function getNearestTarget()
	if isDead then return nil, nil end
	local playerList = Players:GetPlayers()

	local nearestPlayer = nil
	local nearestDistance = nil

	for _, player in pairs(playerList) do
		if player then
			local character = player.Character
			if character then
				local humanoid = character:FindFirstChild("Humanoid")
				local humanoidRootPart = character:FindFirstChild("HumanoidRootPart")
				if humanoid and humanoidRootPart and humanoid.Health > 0 and self:FindFirstChild("HumanoidRootPart") then
					local distance = (humanoidRootPart.Position - self.HumanoidRootPart.Position).Magnitude 
					if distance <= range then
						if nearestDistance == nil or distance < nearestDistance then
							nearestPlayer = player
							nearestDistance = distance
						end
					end
				end
			end
		end
	end

	return nearestPlayer, nearestDistance
end

local function start()
	if isDead then return end
	local nearestPlayer, distance = getNearestTarget()

	if nearestPlayer ~= nil and distance ~= nil then
		local char = nearestPlayer.Character
		if not char then return end
		local humanoid = char:FindFirstChild("Humanoid")
		local humanoidRootPart = char:FindFirstChild("HumanoidRootPart")
		if not humanoid or not humanoidRootPart or humanoid.Health <= 0 or not self:FindFirstChild("HumanoidRootPart") then return end

		if distance > attackrange then
			path:Run(humanoidRootPart)
		elseif distance <= attackrange then

			if path.Status == SimplePath.StatusType.Active then
				path:Stop()
			end

			if tick() - lastAttack >= cooldown then
				if humanoid.Health > 0 then
					lastAttack = tick()
                                      -- this is for variation of punches sounds
					if currentEffectType  == 1 then
						-- do what ever like sound effects vfx animations etc... also change the currentEffectType value to 2 
					elseif currentEffectType  == 2 then
						-- do what ever like sound effects vfx animations etc... also change the currentEffectType to 1
					end
				end
			end

		end

	else
		if self:FindFirstChild("HumanoidRootPart") then
			path:Run(self.HumanoidRootPart)
		end
	end
end

local function onstart()
	if isDead then return end
	start()

	if self:FindFirstChild("Humanoid") and self.Humanoid.Health == 0 then
		--  stops the script if the npc dies, incase of errors
		path.Visualize = false
		isDead = true
		self:Destroy()
	end
end

for _, player in game:GetService("Players"):GetPlayers() do
	task.spawn(onstart, player)
end

while task.wait() do
	if isDead then break end
	onstart()
end
1 Like

i have an issue where some of my npcs just start stutter walking, is there a fix to this

2 Likes

Having the same issue, it seems that it only happens on the first time the Run function is ran.

Edit: I looked into it more and I added a wait(1) before running the function and seems to have fixed it. Might have to do with the fact that I am creating the npc and immediately using the function. Maybe it needs to wait for everything to load?

Edit2: Pathfinding NPC Stutters for no apperant reason, after working just fine - #17 by ErwinK007

Finally found a solid solution. Need to set network ownership of the NPC to server.

i found another solution by using “NoobPath”, its actually smoother than SimplePath.

1 Like

Typo i guess? it’s destroy not destory

This module is fantastic, thank you for making this!


for some reason i made the agentradius high but it still trying to stick to the wall

local Path=SPM.new(N, {AgentRadius=5,AgentHeight=5,WaypointSpacing=1,Costs={wall=math.huge},PathSettings={SupportPartialPath=true}})

2 Likes

I see that simplepath ignores humanoids, making large ammount of npcs stack in one place. How do i disable this feature?

2 Likes

Hello,

Add an attack script on the NPCs so they kill each other …

Enjoy the day!