So I’ve made an NPC Following Script and it works pretty well. Thanks to the suggestions of other devforum users, I was able to speed up the NPC’s Movement and make it more lagless by using run service instead of a while wait() do loop. However, now I’ve encounted a weird bug:
For some reason my NPC just wants to wander quite a bit through the chase, and I can see this by looking at how the NPC randomly turns while following. I feel this is to do with my Character not always being detected during the loop as when I added an else to my character check and made it print if no character was detected, The script printed very ofen, so it calls the wander function. Also I am aware that my wander function isn’t the best. But I’lll post a topic linked to this later. For now here is the code:
local PathfindingService = game:GetService("PathfindingService")
local runservice = game:GetService("RunService")
local Humanoid = script.Parent.Humanoid
local HumanoidRootPart = script.Parent.HumanoidRootPart
local MaxDistance = 50
function FindClosestTarget(MaxDist, TableOfCurrentPlayers)
local Target
local SmallestDistance = MaxDist
local PlayerWithSmallerDistance
for _, player in pairs(TableOfCurrentPlayers) do
local distance = player:DistanceFromCharacter(HumanoidRootPart.Position)
if distance < SmallestDistance then
SmallestDistance = distance
PlayerWithSmallerDistance = player
end
end
Target = PlayerWithSmallerDistance
if Target ~= nil then --Check if it isn't nil
print(Target.Name)
end
return Target
end
function Wander()
local Character = script.Parent
local Humanoid = Character.Humanoid
local Base = workspace.Baseplate
Humanoid:MoveTo(Vector3.new(math.random(-100,100),0,math.random(-100,100)))
end
function FindAndMoveToTargetPos()
local TargetToMoveTo = FindClosestTarget(MaxDistance, game.Players:GetPlayers())
if TargetToMoveTo ~= nil then
local TargetChar = TargetToMoveTo.Character
if TargetChar then
local Target_HRP = TargetChar:WaitForChild("HumanoidRootPart",5)
local path = PathfindingService:CreatePath()
path:ComputeAsync(HumanoidRootPart.Position, Target_HRP.Position)
local waypoint = path:GetWaypoints()
for i, waypoint in pairs(waypoint) do
if waypoint.Action == Enum.PathWaypointAction.Jump then
Humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
Humanoid:MoveTo(waypoint.Position)
Humanoid.MoveToFinished:Wait()
end
else
pcall(function()
FindAndMoveToTargetPos()
end)
end
return TargetToMoveTo
else
Wander()
end
end
function Iterate()
for _, v in pairs(script.Parent:GetChildren()) do
if v:IsA("BasePart") then
v:SetNetworkOwner(nil)
end
end
end
Iterate()
runservice.Heartbeat:Connect(FindAndMoveToTargetPos)
Thanks In Advance!
P.S you can paste this in an R15 Npc and see what I mean. I just want it so my character is always detected. Aa at the moment despite being in range. The NPC still fails to identify my character
You could see exactly what path it is taking and why if you plot the waypoints the NPC is using.
Add the following function:
local function drawPoints(waypoints)
-- Function to DRAW WayPoints
for _, waypoint in pairs(waypoints) do
local part = Instance.new("Part")
part.Shape = "Ball"
part.Material = "Neon"
part.BrickColor = BrickColor.new(0.8,0.2,0.8)
part.Size = Vector3.new(0.6, 0.6, 0.6)
part.Position = waypoint.Position
part.Anchored = true
part.CanCollide = false
part.Parent = game.Workspace
end
end
Then after your local waypoint = path:GetWaypoints() line, add
drawPoints(waypoint)
That will at least show you the path discovered for the NPC to take and might give you a hint as to why it wanders.
I do wonder whether your duplication of the waypoint variable may not be helping at all. One should be a table, the other a single variable. To stop confusion, try renaming:
local waypoints = path:GetWaypoints() -- The table of Waypoints
for i, waypoint in pairs(waypoints) do -- Loop thru a single waypoint from the table of waypoints
if waypoint.Action == Enum.PathWaypointAction.Jump then
Humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
Humanoid:MoveTo(waypoint.Position)
Humanoid.MoveToFinished:Wait()
end
Your suggestion did help. I think the problem isnt my wander function, but the fact that multiple paths are being calculated at once. Maybe because of that my NPC is finding it hard to choose which one to go for. Could simply calling the function whenever the HRP moves while constantly checking if the closest target has changed be a fix. Do you think that would work?
Ok. So I use a ray to detect if anything is in front of the NPC and if it is, I can use the pathfinding to make the NPC move to the player around the obstacle, else I can just directly move to the player.
I’m guessing it’s like turning backward and whatever. It should fix the problem while you’re directly moving towards the player, the jitter comes from the way you pathfind usually
It doesn’t really fix the problem I’m experiencing. My NPC Still Randomly Rotates for a split second while following me. I redesigned the script, so this is the one I’m using now:
local PathfindingService = game:GetService("PathfindingService")
local runservice = game:GetService("RunService")
local Humanoid = script.Parent.Humanoid
local HumanoidRootPart = script.Parent.HumanoidRootPart
local MaxDistance = 50
local DebrisService = game:GetService("Debris")
local DebuggingMode = true -- Shows Path NPC Calculates
local Character = Humanoid.Parent
local RayToCheck = Ray.new(HumanoidRootPart.Position , Vector3.new(0,0, MaxDistance)) --Creating the ray down from the character
function FindClosestTarget(MaxDist, TableOfCurrentPlayers)
local Target
local SmallestDistance = MaxDist
local PlayerWithSmallerDistance
for _, player in pairs(TableOfCurrentPlayers) do
local distance = player:DistanceFromCharacter(HumanoidRootPart.Position)
if distance < SmallestDistance then
SmallestDistance = distance
PlayerWithSmallerDistance = player
end
end
Target = PlayerWithSmallerDistance
if Target ~= nil then --Check if it isn't nil
print(Target.Name)
end
return Target
end
function MoveToTarget(Target)
if Target then
local Target_Char = Target.Character or Target.CharactrAdded:Wait()
if Target_Char ~= nil and Target_Char:IsA("Model") then
local HRP = Target_Char:WaitForChild("HumanoidRootPart", 5)
if HRP then
Humanoid:MoveTo(HRP.Position)
end
end
end
end
function PathfindToTarget(Target)
if Target then
local Target_Char = Target.Character or Target.CharacterAdded:Wait()
if Target_Char ~= nil and Target_Char:IsA("Model") then
local Target_HRP = Target_Char:WaitForChild("HumanoidRootPart",5)
local path = PathfindingService:CreatePath()
path:ComputeAsync(HumanoidRootPart.Position, Target_HRP.Position)
if path.Status == Enum.PathStatus.Success and DebuggingMode then
local WaypointsTable = path:GetWaypoints()
for i, waypoint in pairs(WaypointsTable) do
local part = Instance.new("Part")
part.Shape = "Ball"
part.Material = "Neon"
part.Size = Vector3.new(0.6, 0.6, 0.6)
part.Position = waypoint.Position
part.Anchored = true
part.CanCollide = false
part.Parent = game.Workspace
DebrisService:AddItem(part, 5)
Humanoid:MoveTo(waypoint.Position)
Humanoid.MoveToFinished:Wait()
end
else
local WaypointsTable = path:GetWaypoints()
for i, waypoint in pairs(WaypointsTable) do
Humanoid:MoveTo(waypoint.Position)
Humanoid.MoveToFinished:Wait()
end
end
else
pcall(function()
PathfindToTarget()
end)
end
end
end
function Iterate()
for _, v in pairs(script.Parent:GetChildren()) do
if v:IsA("BasePart") then
v:SetNetworkOwner(nil)
end
end
end
function CheckRayCollisions(RayToCheck, Ray_Hit)
local Closest_Plr
Closest_Plr = FindClosestTarget(MaxDistance, game.Players:GetPlayers())
if Closest_Plr ~= nil then
if Ray_Hit then
if Ray_Hit.Parent == Closest_Plr.Character or Closest_Plr.CharacterAdded:Wait() then
return game.Players:GetPlayerFromCharacter(Ray_Hit.Parent)
else
return nil
end
end
end
end
Iterate()
runservice.Heartbeat:Connect(function()
local Ray_Hit = workspace:FindPartOnRay(RayToCheck, Character)
local Target = FindClosestTarget(MaxDistance, game.Players:GetPlayers())
if Target ~= nil then
if CheckRayCollisions(RayToCheck,Ray_Hit ) ~= nil and CheckRayCollisions(RayToCheck) == Target then
MoveToTarget(Target)
else
PathfindToTarget(Target)
end
end
end)
P.S. To See what i mean put this code in a script. Then put the script in an R15 NPC. That should show you what I’m experiencing
Possibly. I would do some benchmarking around the functions such as MoveToTarget, but I doubt that’s the cause. Everything seems right, except for these two lines (well actually 4 since you repeated it) :
Is there a chance that the humanoid is stopping between each waypoint for a brief second or so. Perhaps you may need to use magnitude calculation for a constant distance value, and call MoveTo when the conditions are met. This may sound like a dumb question, but can you try getting rid of MoveToFinished entirely?
No way. MoveToFinished:Wait() was the root cause for the weird rotations. Thanks For That! Will I still have to implement your ‘magnitude calculation’ idea?
Well, I’m not sure if you want to get rid of MoveToFinished:Wait() entirely. MoveToFinished can be a bit glitchy sometimes, because the Humanoid has to wait until the waypoint is reached, resulting in a brief pause. I suggested magnitude because of issues such as this. A magnitude calculation would just mean that the transition between each waypoint would be smooth, if your updating it constantly, through a repeat wait until loop.