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.
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.
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.
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
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?