NPC stopping in the middle of the way

I’m trying to create an NPC system. At first, it works fine, but after a while, it stops working and won’t move anymore

Script

local npc = script.Parent
local humanoid = npc:FindFirstChild("Humanoid")
local initialPosition = npc.PrimaryPart.Position
local randomWalkRadius = 0 -- Raio de andar aleatoriamente
local detectionRadius = 50 -- Raio de detecção do jogador
local followRadius = 150 -- Raio máximo para seguir o jogador (baseado na initialPosition)
local canJump = true -- Define se o NPC pode pular ou não
local CanNotWalkInPublic = false
local pathfindingService = game:GetService("PathfindingService")
local randomMoveWaitTimeMin = 1
local randomMoveWaitTimeMax = 10

local isMovingRandomly = false
local isFollowingPlayer = false

-- Configurações do Pathfinding
local agentParameters = {
	AgentRadius = 5, -- Raio do NPC (ajuste conforme o tamanho do NPC)
	AgentHeight = 5, -- Altura do NPC (ajuste conforme o tamanho do NPC)
	AgentCanJump = canJump, -- Define se o NPC pode pular
	AgentCanClimb = true, -- Define se o NPC pode escalar
	AgentMaxSlopeAngle = 80, -- Ângulo máximo de inclinação que o NPC pode subir
}

-- Verifica se o jogador está olhando para o NPC
local function isPlayerLooking(player)
	if not CanNotWalkInPublic then return false end -- Se WalkInPublic for falso, ignora essa verificação
	local playerRootPart = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
	if playerRootPart then
		local npcDirection = (npc.PrimaryPart.Position - playerRootPart.Position).Unit
		local playerLookDirection = playerRootPart.CFrame.LookVector
		local dotProduct = npcDirection:Dot(playerLookDirection)
		return dotProduct > 0.5
	end
	return false
end

-- Ancorar ou desancorar todas as Parts do NPC
local function setNPCAnchored(state)
	for _, part in ipairs(npc:GetDescendants()) do
		if part:IsA("BasePart") and part ~= npc.PrimaryPart then
			part.Anchored = state
		end
	end
end

-- Verifica se o jogador está dentro do raio de detecção
local function isPlayerInDetectionRadius()
	for _, player in pairs(game.Players:GetPlayers()) do
		local playerRootPart = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
		if playerRootPart then
			local distance = (playerRootPart.Position - npc.PrimaryPart.Position).Magnitude
			if distance <= detectionRadius then
				return player
			end
		end
	end
	return nil
end

-- Movimento aleatório
function moveRandomly()
	if isMovingRandomly then return end
	isMovingRandomly = true

	local retries = 0
	local moved = false
	while not moved and retries < 5 and not isFollowingPlayer do
		local targetPosition = initialPosition + Vector3.new(
			math.random(-randomWalkRadius, randomWalkRadius),
			0,
			math.random(-randomWalkRadius, randomWalkRadius)
		)

		local path = pathfindingService:CreatePath(agentParameters)
		path:ComputeAsync(npc.PrimaryPart.Position, targetPosition)

		if path.Status == Enum.PathStatus.Success then
			local waypoints = path:GetWaypoints()
			if #waypoints > 0 then
				for _, waypoint in ipairs(waypoints) do
					if isFollowingPlayer then break end
					humanoid:MoveTo(waypoint.Position)
					if waypoint.Action == Enum.PathWaypointAction.Jump then
						humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
					end
					local precision = 6
					local dist = (npc.PrimaryPart.Position - waypoint.Position).Magnitude
					repeat
						task.wait()
						dist = (npc.PrimaryPart.Position - waypoint.Position).Magnitude
					until dist <= precision or isFollowingPlayer
				end
				moved = true
			end
		else
			retries = retries + 1
		end
	end

	if not isFollowingPlayer and moved then
		local waitTime = math.random(randomMoveWaitTimeMin, randomMoveWaitTimeMax)
		local startTime = tick()
		while tick() - startTime < waitTime and not isFollowingPlayer do
			if isPlayerInDetectionRadius() then
				break
			end
			wait(0.1)
		end
	end

	isMovingRandomly = false
end

-- Seguir jogador
function followPlayer(player)
	if isFollowingPlayer then return end -- Evita múltiplas execuções simultâneas
	isFollowingPlayer = true

	local playerRootPart = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
	if playerRootPart then
		local distanceToInitialPosition = (playerRootPart.Position - initialPosition).Magnitude
		if distanceToInitialPosition > followRadius then
			-- Se o jogador estiver fora do raio de seguimento, volta para a posição inicial
			humanoid:MoveTo(initialPosition)
			isFollowingPlayer = false
			return
		end

		local path = pathfindingService:CreatePath(agentParameters)
		path:ComputeAsync(npc.PrimaryPart.Position, playerRootPart.Position)
		if path.Status == Enum.PathStatus.Success then
			local waypoints = path:GetWaypoints()
			if #waypoints > 0 then
				for _, waypoint in ipairs(waypoints) do
					if not isFollowingPlayer then break end -- Interrompe se parar de seguir o jogador
					if isPlayerLooking(player) then
						humanoid:MoveTo(npc.PrimaryPart.Position)
						setNPCAnchored(true)
						isFollowingPlayer = false
						return
					else
						setNPCAnchored(false)
					end

					humanoid:MoveTo(waypoint.Position)
					if waypoint.Action == Enum.PathWaypointAction.Jump then
						humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
					end
					local precision = 6 -- Precisão ajustada para 6
					local dist = (npc.PrimaryPart.Position - waypoint.Position).Magnitude  
					repeat task.wait() dist = (npc.PrimaryPart.Position - waypoint.Position).Magnitude until dist <= precision or not isFollowingPlayer
				end
			end
		end
	end

	isFollowingPlayer = false
end

local function resetNPC()
	setNPCAnchored(false)
	humanoid:MoveTo(initialPosition)
	isMovingRandomly = false
	isFollowingPlayer = false
end

-- Comportamento
function npcBehavior()
	while npc and npc.Parent and humanoid and humanoid.Health > 0 do
		local closestPlayer = isPlayerInDetectionRadius()

		if closestPlayer then
			if isPlayerLooking(closestPlayer) then
				setNPCAnchored(true)
				humanoid:MoveTo(npc.PrimaryPart.Position)
			else
				setNPCAnchored(false)
				followPlayer(closestPlayer)
			end
		else
			setNPCAnchored(false)
			moveRandomly()
		end


	end
end

npcBehavior()

I would recommend debugging it with prints or break points to see if it’s being infinitely yielded from trying to create a path but failing with the PathfindingService.

I’m guessing it could be this piece of code causing it. In your followPlayer Function.
repeat task.wait() dist = (npc.PrimaryPart.Position - waypoint.Position).Magnitude until dist <= precision or not isFollowingPlayer

for a better view

robloxapp-20250228-0031415.wmv (4.4 MB)

I added some prints and said that the NPC is getting stuck on something, but he’s just standing

image

Alr I found so your Humanoid:MoveTo will stop if it doesn’t reach the end. I recommend adding more checks and maybe like Humanoid.MoveToFinished connections since they get fired always even if the MoveTo cancels from what I’ve read in the Humanoid Document here.

I’ve tested the script and can’t really pinpoint where the inf yielding happens. One more thing its good to try using coroutines and yielding when anchoring your npc inside the for i loop instead of always checking. So a way to resume the pathway or making a new one.

This guy shows good tips with making a following pathfinding npc.

1 Like

Right after that jump, there was no way to create a path to the player from that spot.
If no path can be made, it’s getting stuck in an endless loop somewhere after checking for a new possible path. You may need to account for that by returning to moveRandomly() if that happens.

You may also want to look into using an Agent to make off-limits Materials.

local agentParameters = {
	AgentRadius = 5, 
	AgentHeight = 5, 
	AgentCanJump = canJump, 
	AgentCanClimb = true, 
	AgentMaxSlopeAngle = 80, 
	MaterialCosts = {
		[Enum.Material.Concrete] = math.huge,
		[Enum.Material.Metal] = math.huge
	}
}

It may be creating a path it just can’t get to this can help with that along with the script testing for being stuck back to random moves.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.