Hostile NPC keeps glitching

Hello everybody, it’s me again.

I was trying to make a survive the killer type of game where you have to repair certain objects to escape.

I had a lot of issues in the process of making the killer work and now, I’ve encountered a problem I haven’t been able to fix in any way.

As you can see in the video below, the killer isn’t really… responding well. He should chase you as soon as you get close to him and stop when you’re 75 studs away, but he just seems to glitch and not follow you or just standing still. His pathfinding is also pretty laggy.

To fix the problem, I have tried revising the script and looking for if everything was in the right place, but every modify I made didn’t change anything.

Please also note that I’m not an expert at using PathFinding, so it might be just something very obvious.

– Notes:
The killer works like this:

  1. First, he cheks if there are any targets in sight with raycasting.
  2. If there aren’t any, he stars walking between certain waypoints found in the map (Image below), but keeps checking for it.
  3. If there is a target, his walkspeed increases and he starts chasing the player until one of these 3 conditions is met: Either the killer dies, the target dies or the target gets too far
  4. After that, he should start wandering between waypoints as usual.

Map of the waypoints (Waypoints are the red cubes)

Here’s the killer’s code:

-- Other
local map = game.Workspace.CurrentMap
local amtOfWaypoints = 0
local targetFound = false
-- BodyParts
local zombieHum = script.Parent:WaitForChild("Humanoid")
local zombieRootPart = script.Parent:WaitForChild("HumanoidRootPart")
local zombieHead = script.Parent:WaitForChild("Head")
local zombieLowerTorso = script.Parent:WaitForChild("LowerTorso")
-- Anims
local grab = script.Parent:WaitForChild("Grab")
local grabAnim = zombieHum:LoadAnimation(grab)
grabAnim.Priority = Enum.AnimationPriority.Action
-- Sound
local grabSound = zombieHead:WaitForChild("Attack")
-- local scream = zombieHead:WaitForChild("Scream")

local clone = script.Parent:Clone()

-- Tables
local walkRandomlyWayPoints = {}
-- Code
-- Adds the waypoints to the table
for i2, v2 in pairs(workspace[map.Value].Waypoints:GetChildren()) do
	amtOfWaypoints = amtOfWaypoints + 1
	table.insert(walkRandomlyWayPoints,amtOfWaypoints,v2.Position)
end

function walkRandomly()
	--local xRand = math.random(-100,100)
	--local zRand = math.random(-100,100)
	local posRand = walkRandomlyWayPoints[math.random(1,amtOfWaypoints)]
	local goal = Vector3.new(posRand.X,0,posRand.Z)
	--local goal = Vector3.new(xRand,0,zRand)

	local path = game:GetService("PathfindingService"):CreatePath()
	path:ComputeAsync(zombieRootPart.Position, goal)
	local waypoints = path:GetWaypoints()
	
	if path.Status == Enum.PathStatus.Success then
		for _, waypoint in ipairs(waypoints) do
			zombieHum:MoveTo(waypoint.Position)
			local timeOut = zombieHum.MoveToFinished:Wait()
			if targetFound == true then
				break
			end
			if not timeOut then
				walkRandomly()
			end
		end
	else
		wait()
		if targetFound == false then
			walkRandomly()
		end
	end
end

function findPath(target)
	local path = game:GetService("PathfindingService"):CreatePath()
	path:ComputeAsync(zombieRootPart.Position,target.Position)
	local waypoints = path:GetWaypoints()

	if path.Status == Enum.PathStatus.Success then
		for _, waypoint in ipairs(waypoints) do
			zombieHum:MoveTo(waypoint.Position)
			local timeOut = zombieHum.MoveToFinished:Wait(1)
			if not timeOut then
				findPath(target)
				break
			end
			if checkSight(target) then
				repeat
					zombieHum:MoveTo(target.Position)
					attack(target)
					wait(0.2)
					if target == nil then
						break
					elseif target.Parent == nil then
						break
					elseif (zombieRootPart.Position - target.Position).magnitude < 75 then
						targetFound = false
					end
				until checkSight(target) == false or zombieHum.Health < 1 or target.Parent.Humanoid.Health < 1 or (zombieRootPart.Position - target.Position).magnitude < 75
			end
			if (zombieRootPart.Position - waypoints[1].Position).magnitude > 20 then
				findPath(target)
				break
			end
		end
	end
end

function checkSight(target)
	local ray = Ray.new(zombieRootPart.Position, (target.Position - zombieRootPart.Position).Unit * 40)
	local hit,position = workspace:FindPartOnRayWithIgnoreList(ray, {script.Parent})
	if hit then
		if hit:IsDescendantOf(target.Parent) and math.abs(hit.Position.Y - zombieRootPart.Position.Y) < 4 then
			return true
		end
	end
	return false
end

function findTarget()
	local dist = 50
	local target = nil
	local potentialTargets = {}
	local seeTargets = {}
	for i,v in ipairs(workspace:GetChildren()) do
		local human = v:FindFirstChild("Humanoid")
		local torso = v:FindFirstChild("Torso") or v:FindFirstChild("HumanoidRootPart")
		if human and torso and v.Name ~= script.Parent.Name then
			if (zombieRootPart.Position - torso.Position).magnitude < dist and human.Health > 0 then
				table.insert(potentialTargets,torso)
			end
		end
	end
	if #potentialTargets > 0 then
		for i,v in ipairs(potentialTargets) do
			if checkSight(v) then
				table.insert(seeTargets, v)
			elseif #seeTargets == 0 and (zombieRootPart.Position - v.Position).magnitude < dist then
				target = v
				dist = (zombieRootPart.Position - v.Position).magnitude
			end
		end
	end
	if #seeTargets > 0 then
		dist = 200
		for i,v in ipairs(seeTargets) do
			if (zombieRootPart.Position - v.Position).magnitude < dist then
				target = v
				dist = (zombieRootPart.Position - v.Position).magnitude
			end
		end
	end
	return target
end

function attack(target)
	if (zombieRootPart.Position - target.Position).magnitude < 5 then
		grabAnim:Play()
		grabSound:Play()
		if target.Parent ~= nil then
			target.Parent.Humanoid:TakeDamage(100)
		end
		wait(0.4)
	end
end

function died()
	zombieRootPart.CloseSound.Playing = false
	wait(5)
	zombieRootPart.CloseSound.Playing = true
	clone.Parent = workspace
	game:GetService("Debris"):AddItem(script.Parent,0.1)
end

zombieHum.Died:Connect(died)

spawn(function()
	while wait(1.5) do
		if zombieHum.Health < 1 then
			break
		end
		main2()
	end
end)

function main()
	local target = findTarget()
	if target then
		zombieHum.WalkSpeed = 12
		findPath(target)
	else
		zombieHum.WalkSpeed = 6
		walkRandomly()
	end
end

function main2()
	local target = findTarget()
	if target then
		zombieHum.WalkSpeed = 12
		findPath(target)
	end
end

while wait(0.1) do
	if zombieHum.Health < 1 then
		break
	end
	main()
end

Thanks in advance to anyone helping. Have a great day!

Okay so I think I see a problem:
I noticed that you have two loops at the bottom of your script.

Spawn(function()
-- and
while wait(0.1) do

they both call a main function and I think whats happening is that it follows player then walks randomly
then back to player in an infinite loop… because they are loops xd
so im thinking reduce the loop count into one

while wait do

and just check more things in the first loop / add a bool to run one main function at a time or somthin

PS: Game looks GREAT

Hi DamSam,
First of all, thanks for telling me the game looks good! I really put effort into it.
Second of all, I did some debugging with print() and other tools and found out that it’s true, it is switching constantly between walkRandomly() and findPath(target). I’m currently working on a fix but, if I’m still not able to fix it, I’m probably gonna post here soon. Thanks again!

1 Like

Yea np ill check in once and a while.

1 Like