Pathfinding no Roblox: ligue a malha de navegação antes de escrever código
Quase todo bug de NPC que não anda direito é visível antes de qualquer script: o Studio desenha a malha de navegação e as setas de pulo. Quem liga isso primeiro depura com os olhos.
Produzido com IA a partir das fontes citadas abaixo, e conferido por checagens automáticas contra essas mesmas fontes. Nenhuma pessoa leu este texto antes da publicação. Política Editorial

NPC que anda contra a parede, que para no meio do caminho, que pula onde não deveria pular — a lista de sintomas de pathfinding quebrado é longa, e a reação padrão é abrir o script e encher de print.
Na maior parte das vezes, o problema é visível antes de qualquer código. O Studio desenha a malha de navegação na tela, e quem liga isso primeiro depura com os olhos.
O problema
A definição oficial já entrega a estrutura do assunto: pathfinding é mover um personagem ou objeto — o agente — por um caminho lógico em volta de obstáculos até um destino, opcionalmente evitando materiais perigosos ou regiões definidas.
Três partes, e cada uma é um lugar onde algo pode dar errado. O agente tem um corpo com tamanho. Os obstáculos definem por onde dá para passar. E os materiais e regiões definem por onde vale a pena passar, o que é diferente de poder.
A diferença entre isso e mover o NPC na direção do alvo é só uma: o que acontece quando existe uma parede. MoveTo na direção do alvo anda contra a parede para sempre; o caminho calculado dá a volta. Todo o resto da complexidade existe para sustentar essa única diferença.
E o motor não calcula o caminho olhando o seu cenário como você o vê. Ele usa uma malha de navegação, gerada a partir da geometria, que é a representação do que é chão andável. Quando o NPC se comporta de um jeito que não faz sentido, é quase sempre porque a malha discorda de você sobre o que é chão.
Passo a passo
1. Ligue a visualização antes de tudo. O Studio consegue desenhar a malha de navegação e os rótulos de modificador. As opções ficam no widget de Visualization Options, no canto superior direito do viewport 3D.
Com a malha ligada, as áreas coloridas mostram onde o personagem pode andar ou nadar. E as setinhas indicam áreas que o personagem vai tentar alcançar pulando.
Esses dois sinais respondem, de olho, as duas perguntas que mais consomem tempo: "por que ele não vai até ali?" — porque ali não é área colorida — e "por que ele pulou?" — porque há uma seta ali.
Este passo é primeiro de propósito. Depurar pathfinding sem a malha ligada é depurar um algoritmo cujo dado de entrada você não está vendo.
2. Crie o caminho com os parâmetros do agente. O pathfinding se implementa pelo PathfindingService e métodos como o CreatePath. O exemplo oficial cria o caminho com AgentCanClimb ligado e um custo para a água.
Os parâmetros do agente descrevem o corpo que vai andar: raio, altura, se pula, se escala. Eles não são enfeite — é com eles que o cálculo decide se o agente cabe numa passagem.
3. Use custos em vez de proibições. O Costs da criação do caminho é o mecanismo mais subutilizado. Ele não diz "não passe"; diz "passar aqui custa mais". Um custo alto na água faz o agente preferir o caminho seco quando existe um, e ainda assim atravessar quando não existe.
É a diferença entre um NPC que dá a volta no lago e um NPC que desiste porque o único caminho passava por ele.
4. Reaja ao bloqueio. Caminho calculado é uma foto do mundo naquele instante. Se uma porta fecha, se um jogador põe um bloco, se outro NPC para no meio — o caminho continua válido no papel e errado no mundo.
5. Para testar com o personagem do jogador, como faz o exemplo da documentação, é preciso pôr DevComputerMovementMode e DevTouchMovementMode em Scriptable, e o código vai num LocalScript dentro de StarterCharacterScripts. Sem isso, o controle do jogador e o script disputam o mesmo personagem.
Esse detalhe merece atenção porque ele explica um resultado confuso. Sem pôr os dois modos em Scriptable, o script de pathfinding roda, calcula tudo certo, manda o personagem andar — e o sistema de controle padrão manda ele parar no quadro seguinte. O personagem treme no lugar, e não há nada de errado com o caminho.
Vale também entender por que o exemplo oficial usa o personagem do jogador em vez de um NPC, o que parece uma escolha estranha para uma página sobre mover personagens. O motivo é didático: com o jogador, dá para ver de dentro o caminho sendo percorrido e sentir quando o movimento fica errado. Para código de produção, o mesmo padrão vale para NPC, do lado do servidor, e sem mexer em nenhum modo de movimento.
Artefato
O script abaixo é um NPC que persegue um alvo, e ele existe para mostrar as três coisas que quase todo exemplo simples de pathfinding deixa de fora.
A primeira: recalcular quando o caminho é bloqueado. O evento existe, e sem ele o NPC caminha confiante para uma parede que apareceu depois.
A segunda: recalcular quando o alvo andou. Perseguir um jogador com um caminho calculado há dez segundos é perseguir onde ele estava.
A terceira, e a que mais salva: desistir de um ponto que demora demais. Um NPC preso num canto, esperando um MoveToFinished que nunca vem, trava a rotina inteira e nunca mais anda. Com prazo, ele desiste, recalcula, e o jogador nunca vê o problema.
Erro comum
O erro mais comum é assumir que a malha de navegação é o cenário. Ela não é — é uma derivação dele, e há coisas que a fazem discordar do que você vê.
O sintoma clássico: o NPC se recusa a entrar numa área que claramente tem chão. Com a visualização ligada, o motivo aparece em um segundo — aquela área não está colorida. Pode ser geometria fina demais, pode ser uma parte sem colisão, pode ser um teto baixo que não dá a altura do agente. Sem a visualização, isso vira meia hora de hipóteses.
O segundo erro é sobre o tamanho do agente. Os parâmetros padrão descrevem um personagem padrão. Um NPC maior que isso recebe caminhos que passam por vãos onde ele não cabe — e então ele trava no vão, fisicamente, enquanto o algoritmo insiste que aquele é o caminho. O sintoma é um NPC empurrando uma quina.
O terceiro é o mais sutil e está no coração do exemplo oficial: decidir que um ponto foi alcançado é mais difícil do que parece. O exemplo da documentação trata o personagem no ar como ponto ainda não alcançado, constrói um plano do próximo ponto em direção ao atual, achata a normal desse plano em relação ao eixo Y quando não está escalando, e calcula um limiar a partir da velocidade do personagem em relação ao plano — considerando o ponto alcançado quando a distância fica abaixo desse limiar à frente do plano.
Isso é muito mais elaborado que "a distância é menor que dois studs", e a elaboração tem motivo. Um personagem rápido percorre mais que o raio de tolerância entre um quadro e outro, passa direto do ponto sem nunca estar perto o bastante, e fica oscilando de um lado para o outro. O limiar proporcional à velocidade resolve isso.
A lição prática, mesmo para quem não vai copiar aquele código: se o seu NPC fica tremendo em volta de um ponto, o problema não é o caminho. É o critério de chegada.
O quarto erro é recalcular demais. Cada cálculo tem custo, e um NPC que recalcula todo quadro consome o servidor sem andar melhor. Recalcular por evento — bloqueio, alvo que se moveu além de um limite, prazo estourado — é o meio-termo que funciona.
Próximo passo
O recurso que rende mais depois do básico são os modificadores de pathfinding, que é a segunda opção do widget de visualização. Eles são o que permite dizer "esta região é perigosa" ou "prefira este corredor" sem mexer na geometria — e os rótulos aparecem na tela, o que torna a configuração conferível.
Combinados com o Costs, eles transformam o pathfinding de um algoritmo que acha o caminho mais curto num sistema que acha o caminho que faz sentido no seu jogo: o guarda que patrulha pelo corredor iluminado, o bicho que evita a água, o NPC que não passa pela sala do chefe.
Antes disso, porém, vale o exercício de cinco minutos: abra o seu mapa, ligue a malha de navegação, e ande pelo cenário olhando. Quase todo mundo encontra pelo menos um lugar onde a malha discorda do que achava — e esse lugar costuma ser exatamente onde os NPCs vinham se comportando mal.
Fontes
- Pathfinding | Documentation - Roblox Creator Hub — consultada em
Artefato pronto pra usar
-- ServerScriptService: NPC que segue um alvo usando PathfindingService.
--
-- A diferença entre isto e um MoveTo na direção do alvo é o que
-- acontece quando existe uma parede: MoveTo anda contra ela para
-- sempre, o caminho calculado dá a volta.
--
-- Três coisas que este script faz e que a maioria esquece:
-- 1. RECALCULA quando o caminho é bloqueado no meio
-- 2. RECALCULA quando o alvo se move demais desde o cálculo
-- 3. DESISTE do ponto se demorar tempo demais para alcançá-lo
-- (sem isso, um NPC preso num canto fica preso para sempre)
local PathfindingService = game:GetService("PathfindingService")
local npc = script.Parent
local humanoid = npc:WaitForChild("Humanoid")
local raiz = npc:WaitForChild("HumanoidRootPart")
local RECALCULAR_SE_ALVO_ANDAR = 8 -- studs
local PRAZO_POR_PONTO = 8 -- segundos
local PAUSA = 0.1
-- Os parâmetros do agente descrevem o CORPO que vai andar. Errar o
-- tamanho aqui produz caminho que atravessa vão onde o NPC não cabe.
local caminho = PathfindingService:CreatePath({
AgentRadius = 2,
AgentHeight = 5,
AgentCanJump = true,
AgentCanClimb = false,
Costs = {
Water = 20, -- atravessa, mas só se não houver caminho seco
Perigo = math.huge, -- PathfindingModifier chamado "Perigo"
},
})
local bloqueado = false
caminho.Blocked:Connect(function()
bloqueado = true
end)
local function alvoAtual()
local alvo = workspace:FindFirstChild("Alvo")
return alvo and alvo:IsA("BasePart") and alvo.Position or nil
end
local function irAte(destino)
local ok = pcall(function()
caminho:ComputeAsync(raiz.Position, destino)
end)
if not ok or caminho.Status ~= Enum.PathStatus.Success then
return false
end
local pontos = caminho:GetWaypoints()
bloqueado = false
-- começa no 2: o primeiro ponto é onde o NPC já está
for i = 2, #pontos do
if bloqueado then
return false
end
local destinoNovo = alvoAtual()
if destinoNovo and (destinoNovo - destino).Magnitude > RECALCULAR_SE_ALVO_ANDAR then
return false -- o alvo andou: vale mais recalcular do que insistir
end
local ponto = pontos[i]
if ponto.Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
humanoid:MoveTo(ponto.Position)
-- MoveToFinished com prazo: sem ele, NPC presso num canto trava
-- a rotina inteira e nunca mais anda.
local chegou = humanoid.MoveToFinished:Wait()
if not chegou then
return false
end
end
return true
end
task.spawn(function()
while npc.Parent and humanoid.Health > 0 do
local destino = alvoAtual()
if destino then
irAte(destino)
end
task.wait(PAUSA)
end
end)


