Roblox pathfinding issues

The problem
My AI is not working very well ,sometimes she starts walking slowly (after killing a player), and sometimes she gets stuck on a wall.

local teddy = script.Parent
local humanoid = teddy:WaitForChild("Humanoid")
local root = teddy:WaitForChild("HumanoidRootPart")
local animFolder = teddy:WaitForChild("Anims")
local pathService = game:GetService("PathfindingService")
local players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

teddy.PrimaryPart:SetNetworkOwner(nil)

-- Sons
local chaseSound = teddy:FindFirstChild("Chase")

-- Som passos
local footstepsSound = root:FindFirstChild("Footsteps")
if not footstepsSound then
	footstepsSound = Instance.new("Sound")
	footstepsSound.Name = "Footsteps"
	footstepsSound.SoundId = "rbxassetid://128581675687648"
	footstepsSound.Parent = root
end
footstepsSound.MaxDistance = 90
footstepsSound.EmitterSize = 10
footstepsSound.RollOffMode = Enum.RollOffMode.Linear
footstepsSound.Volume = 10
footstepsSound.Looped = true

local isFootstepsPlaying = false
local isInteracting = false

local function updateFootstepsSound()
	local speed = root.Velocity.Magnitude
	if speed > 1 then
		if not isFootstepsPlaying then
			footstepsSound:Play()
			isFootstepsPlaying = true
		end
	else
		if isFootstepsPlaying then
			footstepsSound:Stop()
			isFootstepsPlaying = false
		end
	end
end

-- Waypoints
local allWaypoints = workspace:WaitForChild("waypoints"):GetChildren()
local availableWaypoints = {}

local patrolSpeed = 18
local chaseSpeed = 22
local closeRangeSpeed = 10

-- Anti-stuck
local lastPos = root.Position
local stuckTime = 0
local stuckThreshold = 2
local minMoveDistance = 1

local currentTarget = nil
local lastKnownPos = nil
local targetLostTime = 0
local targetLoseDelay = 3

-- Funções de waypoint
local function resetAvailableWaypoints()
	availableWaypoints = {}
	for _, point in ipairs(allWaypoints) do
		table.insert(availableWaypoints, point)
	end
	for i = #availableWaypoints, 2, -1 do
		local j = math.random(1, i)
		availableWaypoints[i], availableWaypoints[j] = availableWaypoints[j], availableWaypoints[i]
	end
end
resetAvailableWaypoints()

local function getNextWaypoint()
	if #availableWaypoints == 0 then
		resetAvailableWaypoints()
	end
	return table.remove(availableWaypoints)
end

-- Função de visão
local function canSeeTarget(target)
	if not target or not target:FindFirstChild("HumanoidRootPart") then return false end
	local origin = root.Position
	local maxSeeDistance = 150
	local direction = (target.HumanoidRootPart.Position - origin)
	if direction.Magnitude > maxSeeDistance then
		return false
	end
	direction = direction.Unit * maxSeeDistance
	local ray = Ray.new(origin, direction)
	local hit = workspace:FindPartOnRay(ray, teddy)
	return hit and hit:IsDescendantOf(target)
end

local function findTarget()
	local maxDistance = 150
	local nearestTarget = nil

	-- Jogadores
	for _, player in pairs(players:GetPlayers()) do
		local char = player.Character
		if char and char:FindFirstChild("Humanoid") and char:FindFirstChild("HumanoidRootPart") then
			if char.Humanoid.Health > 0 and char:GetAttribute("Invisible") ~= true then
				local dist = (root.Position - char.HumanoidRootPart.Position).Magnitude
				if dist <= maxDistance and canSeeTarget(char) then
					maxDistance = dist
					nearestTarget = char
				end
			end
		end
	end

	-- DummyBait
	for _, model in pairs(workspace:GetChildren()) do
		if model:IsA("Model") and model ~= teddy and model:FindFirstChild("Humanoid") and model:FindFirstChild("HumanoidRootPart") then
			if model:GetAttribute("DummyBait") == true then
				local dist = (root.Position - model.HumanoidRootPart.Position).Magnitude
				if dist <= maxDistance and canSeeTarget(model) then
					maxDistance = dist
					nearestTarget = model
				end
			end
		end
	end

	return nearestTarget
end

-- Pontos exclusivos com rotação fixa para todos os pontos 1-10
local exclusivePoints = {
	["Point1"] = Vector3.new(1,0,0),
	["Point2"] = Vector3.new(-1,0,0),
	["Point3"] = Vector3.new(0,0,1),
	["Point4"] = Vector3.new(1,0,1),
	["Point5"] = Vector3.new(-1,0,1),
	["Point6"] = Vector3.new(0,0,-1),
	["Point7"] = Vector3.new(1,0,0),
	["Point8"] = Vector3.new(-1,0,-1),
	["Point9"] = Vector3.new(0,0,1),
	["Point10"] = Vector3.new(0,0,1),
}

-- Função de interação com animação de 10s
local function playAnimationFor(pointName)
	local pointNum = string.match(pointName, "Point(%d+)")
	local animName = "Interact" .. pointNum
	local soundName = "InteractSound" .. pointNum

	local animObj = animFolder:FindFirstChild(animName)
	local soundObj = teddy:FindFirstChild(soundName)
	local point = workspace:WaitForChild("waypoints"):FindFirstChild(pointName)

	if animObj and point then
		humanoid.WalkSpeed = 0
		isInteracting = true

		humanoid:MoveTo(point.Position)
		humanoid.MoveToFinished:Wait()

		-- Rotação usando exclusivePoints de todos os pontos
		if exclusivePoints[pointName] then
			local lookVector = exclusivePoints[pointName]
			root.CFrame = CFrame.new(root.Position, root.Position + lookVector)
		end

		-- Toca som de interação
		if soundObj and soundObj:IsA("Sound") then
			soundObj:Play()
		end

		-- Toca animação
		local anim = humanoid:LoadAnimation(animObj)
		anim:Play()

		-- Espera 10 segundos antes de parar
		local elapsed = 0
		local duration = 10
		while elapsed < duration do
			local delta = RunService.Heartbeat:Wait()
			elapsed = elapsed + delta
			updateFootstepsSound()
		end

		anim:Stop()
		isInteracting = false
		humanoid.WalkSpeed = patrolSpeed
	else
		warn("Animação ou ponto não encontrados:", animName, pointName)
	end
end

-- Função de ataque com jumpscare primeiro, depois animação
local function attackTarget(target)
	if isInteracting or not target or not target:FindFirstChild("HumanoidRootPart") then return end

	local dist = (root.Position - target.HumanoidRootPart.Position).Magnitude

	if dist > 8 then
		humanoid.WalkSpeed = chaseSpeed
		humanoid:MoveTo(target.HumanoidRootPart.Position)
		if not target:GetAttribute("DummyBait") then
			if chaseSound and not chaseSound.IsPlaying then
				chaseSound:Play()
			end
		end
	else
		humanoid.WalkSpeed = 0 -- NPC fica parado

		-- Dispara jumpscare e mata jogador imediatamente
		if players:GetPlayerFromCharacter(target) then
			local player = players:GetPlayerFromCharacter(target)
			target.Humanoid.Health = 0

			local jumpscareEvent = ReplicatedStorage:FindFirstChild("JumpscareEventGrandpa")
			if jumpscareEvent then
				jumpscareEvent:FireClient(player)
			end
		end

		-- Para o chaseSound imediatamente
		if chaseSound and chaseSound.IsPlaying then
			chaseSound:Stop()
		end

		-- Reseta alvo e posição antiga para não travar
		currentTarget = nil
		lastKnownPos = nil
		targetLostTime = 0

		-- Toca animação de ataque visual
		local attackAnim = Instance.new("Animation")
		attackAnim.AnimationId = "rbxassetid://122003013927263"
		local animTrack = humanoid:LoadAnimation(attackAnim)
		animTrack:Play()
		animTrack.Stopped:Wait() -- espera terminar visual

		humanoid.WalkSpeed = patrolSpeed
	end
end

-- Caminhar usando Pathfinding
local function walkTo(destination)
	local path = pathService:CreatePath()
	path:ComputeAsync(root.Position, destination.Position)
	if path.Status == Enum.PathStatus.Success then
		local waypoints = path:GetWaypoints()
		for _, waypoint in ipairs(waypoints) do
			humanoid:MoveTo(waypoint.Position)
			local reached = false
			local connection = humanoid.MoveToFinished:Connect(function(r)
				reached = true
			end)
			local elapsed = 0
			local timeout = 3
			while not reached and elapsed < timeout do
				RunService.Heartbeat:Wait()
				elapsed = elapsed + 0.1
				updateFootstepsSound()
				local target = findTarget()
				if target then
					connection:Disconnect()
					attackTarget(target)
					return true
				end
			end
			connection:Disconnect()
		end
	else
		humanoid:MoveTo(destination.Position)
	end
	return false
end

-- Anti-stuck aprimorado
local function antiStuck()
	local distMoved = (root.Position - lastPos).Magnitude
	if distMoved < minMoveDistance then
		stuckTime = stuckTime + 0.5
	else
		stuckTime = 0
	end
	lastPos = root.Position

	if stuckTime >= stuckThreshold then
		warn("NPC preso! Tentando corrigir...")
		stuckTime = 0

		-- Tenta mover para um waypoint aleatório próximo
		local safePoint = getNextWaypoint()
		if safePoint then
			humanoid.WalkSpeed = patrolSpeed
			humanoid:MoveTo(safePoint.Position)
		end

		-- Caso continue preso, teleporta levemente para frente
		local forward = root.CFrame.LookVector
		root.CFrame = root.CFrame + forward * 5
		humanoid:MoveTo(root.Position)
	end
end

-- Atualiza alvo
local function updateTarget()
	local found = findTarget()
	if found then
		currentTarget = found
		lastKnownPos = found.HumanoidRootPart.Position
		targetLostTime = 0
	else
		if currentTarget then
			if currentTarget:FindFirstChild("Humanoid") and currentTarget.Humanoid.Health > 0 then
				targetLostTime = targetLostTime + 0.25
			else
				currentTarget, lastKnownPos, targetLostTime = nil, nil, 0
			end
			if targetLostTime >= targetLoseDelay then
				currentTarget, lastKnownPos, targetLostTime = nil, nil, 0
			end
		end
	end
end

-- Loop principal
while true do
	wait(0.25)
	updateFootstepsSound()
	antiStuck()
	updateTarget()

	if currentTarget then
		if canSeeTarget(currentTarget) then
			attackTarget(currentTarget)
		else
			if lastKnownPos then
				humanoid.WalkSpeed = chaseSpeed
				humanoid:MoveTo(lastKnownPos)
			end
		end
	elseif lastKnownPos then
		humanoid.WalkSpeed = chaseSpeed
		humanoid:MoveTo(lastKnownPos)
		lastKnownPos = nil
	else
		humanoid.WalkSpeed = patrolSpeed
		if chaseSound and chaseSound.IsPlaying then
			chaseSound:Stop()
		end
		local point = getNextWaypoint()
		if point then
			local interrupted = walkTo(point)
			if not interrupted and point.Name ~= "POINTT" then
				playAnimationFor(point.Name)
			end
		end
	end
end
1 Like

I can’t find main issue (i’m in mobile).

Althought you can use while task.wait() end (task.wait() is on 60 fps max, meaning unlike while true do, it has max) on loop

2 Likes