Busca de Caminhos

*Este conteúdo é traduzido por IA (Beta) e pode conter erros. Para ver a página em inglês, clique aqui.

Busca de Caminhos é o processo de mover um personagem ou objeto (agente) ao longo de um caminho lógico em torno de obstáculos para alcançar um destino, evitando opcionalmente materiais perigosos ou regiões definidas.

Visualização de Navegação

Para ajudar com o layout e depuração da busca de caminhos, o Studio pode renderizar uma malha de navegação e rótulos de modificadores. Para habilitá-los, ative Malha de Navegação e Modificadores de Busca de Caminhos no widget de Opções de Visualização no canto superior direito da viewport 3D.

Uma visão de perto da viewport 3D com o botão Opções de Visualização indicado no canto superior direito.

Com Malha de Navegação habilitada, áreas coloridas mostram onde um personagem pode andar ou nadar. Pequenas setas indicam áreas que um personagem tentará alcançar pulando.

Malha de navegação mostrada no Studio

Implementação

Embora a busca de caminhos possa ser implementada de várias maneiras através de PathfindingService e seus métodos associados, como CreatePath(), esta seção usa o seguinte script de busca de caminhos para o personagem do jogador.

Para testar enquanto lê:

  1. IMPORTANTE
    No Explorer, selecione o contêiner StarterPlayer. Em seguida, na janela de Propriedades, defina ambos DevComputerMovementMode e DevTouchMovementMode para Scriptable.

  2. Copie o seguinte código para um LocalScript dentro de StarterCharacterScripts, ou obtenha este pacote e insira-o em StarterCharacterScripts.

    PlayerPathFollow (LocalScript in StarterCharacterScripts)

    local PathfindingService = game:GetService("PathfindingService")
    local Players = game:GetService("Players")
    local RunService = game:GetService("RunService")
    local DESTINATION = Vector3.new(20, 0.5, 20)
    local GROUND_WAIT = 0.01
    local VELOCITY_MULTIPLIER = 0.0625
    local path = PathfindingService:CreatePath({
    AgentCanClimb = true,
    Costs = {
    Water = 20
    }
    })
    local character = script.Parent
    local humanoid = character:WaitForChild("Humanoid")
    local waypoints
    local nextWaypointIndex
    local blockedConnection
    local currentWaypointReachedConnection
    local currentWaypointPlaneNormal = Vector3.zero
    local currentWaypointPlaneDistance = 0
    local pathfinderWorking = false
    local function disconnectCurrentWaypointReachedConnection()
    if not currentWaypointReachedConnection then return end
    currentWaypointReachedConnection:Disconnect()
    currentWaypointReachedConnection = nil
    end
    local function isCurrentWaypointReached()
    if humanoid.FloorMaterial == Enum.Material.Air then
    return false
    end
    local reached = false
    if currentWaypointPlaneNormal ~= Vector3.zero then
    -- Compute the distance from humanoid to destination plane
    local dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance
    -- Compute the component of the humanoid velocity that is towards the plane
    local velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)
    -- Compute the threshold from the destination plane based on humanoid velocity
    local threshold = math.max(1.0, VELOCITY_MULTIPLIER * velocity)
    -- Consider waypoint reached if less then threshold in front of the plane
    reached = dist < threshold
    else
    reached = true
    end
    if reached then
    currentWaypointPlaneNormal = Vector3.zero
    currentWaypointPlaneDistance = 0
    moveToNextWaypoint()
    end
    end
    local function calculateNextWaypointApproach()
    nextWaypointIndex += 1
    if nextWaypointIndex > #waypoints then
    return false
    end
    local currentWaypoint = waypoints[nextWaypointIndex - 1]
    local nextWaypoint = waypoints[nextWaypointIndex]
    -- Build destination plane from next waypoint towards current one
    currentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position
    -- Set normal perpendicular to Y plane when not climbing up
    if nextWaypoint.Label ~= "Climb" then
    currentWaypointPlaneNormal = Vector3.new(currentWaypointPlaneNormal.X, 0, currentWaypointPlaneNormal.Z)
    end
    if currentWaypointPlaneNormal.Magnitude > 0.000001 then
    currentWaypointPlaneNormal = currentWaypointPlaneNormal.Unit
    currentWaypointPlaneDistance = currentWaypointPlaneNormal:Dot(nextWaypoint.Position)
    end
    return true
    end
    local function resetWaypointData()
    humanoid:Move(Vector3.zero)
    currentWaypointPlaneNormal = Vector3.zero
    currentWaypointPlaneDistance = 0
    disconnectCurrentWaypointReachedConnection()
    pathfinderWorking = false
    end
    local function waitForGround()
    while humanoid.FloorMaterial == Enum.Material.Air do
    task.wait(GROUND_WAIT)
    end
    end
    function moveToNextWaypoint()
    if calculateNextWaypointApproach() then
    disconnectCurrentWaypointReachedConnection()
    currentWaypointReachedConnection = RunService.Heartbeat:Connect(isCurrentWaypointReached)
    local nextWaypointPosition = waypoints[nextWaypointIndex].Position
    local nextWaypointAction = waypoints[nextWaypointIndex].Action
    humanoid:Move(nextWaypointPosition - humanoid.RootPart.Position)
    if waypoints[nextWaypointIndex + 1] and waypoints[nextWaypointIndex + 1].Label == "UseBoat" then
    nextWaypointIndex += 1
    -- Call your own customized function to make agent use the boat
    elseif nextWaypointAction == Enum.PathWaypointAction.Jump then
    humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
    while humanoid.FloorMaterial ~= Enum.Material.Air do
    task.wait(GROUND_WAIT)
    end
    humanoid:Move(nextWaypointPosition - humanoid.RootPart.Position)
    end
    else
    resetWaypointData()
    end
    end
    local function findStartingPoint(waypoints)
    nextWaypointIndex = 1
    while nextWaypointIndex + 1 <= #waypoints do
    local dist = waypoints[nextWaypointIndex + 1].Position - humanoid.RootPart.Position
    dist = Vector3.new(dist.X, 0, dist.Z)
    if dist.magnitude >= 2 then
    return
    end
    nextWaypointIndex += 1
    end
    end
    local function followPath()
    -- Compute the path
    pathfinderWorking = true
    waitForGround()
    local success, errorMessage = pcall(function()
    path:ComputeAsync(character.PrimaryPart.Position, DESTINATION)
    end)
    if not success or path.Status ~= Enum.PathStatus.Success then
    warn("Caminho não computado!", errorMessage)
    return
    end
    -- Get the path waypoints
    waypoints = path:GetWaypoints()
    -- Detect if path becomes blocked
    blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
    -- Check if the obstacle is further down the path
    if blockedWaypointIndex >= nextWaypointIndex then
    -- Stop detecting path blockage until path is re-computed
    blockedConnection:Disconnect()
    resetWaypointData()
    -- Call function to re-compute new path
    followPath()
    end
    end)
    findStartingPoint(waypoints)
    moveToNextWaypoint()
    end
    followPath()
  3. Edite a variável DESTINATION (

    LINHA 5
    ) para um destino Vector3 dentro do mundo 3D que o personagem jogador possa alcançar.

  4. Prossiga pelas seções a seguir para aprender sobre a computação de caminhos e movimentação de personagens.

Criação de Caminhos

A busca de caminhos é iniciada através de PathfindingService e seu método CreatePath() (

LINHAS 9–14
). Este método aceita uma tabela opcional de parâmetros que ajustam como o personagem (agente) se move ao longo do caminho.

ChaveDescriçãoTipoPadrão
AgentRadiusRaio do agente, em studs. Útil para determinar a separação mínima de obstáculos.inteiro2
AgentHeightAltura do agente, em studs. Espaço vazio menor que este valor, como o espaço sob escadas, será marcado como não transponível.inteiro5
AgentCanJumpDetermina se pular durante a busca de caminhos é permitido.booleanotrue
AgentCanClimbDetermina se escalar TrussParts durante a busca de caminhos é permitido. Um caminho escalável tem um Label nomeado Climb e o custo para um caminho escalável é 1 por padrão.booleanofalse
WaypointSpacingEspaçamento entre waypoints intermediários no caminho. Se definido como math.huge, não haverá waypoints intermediários.número4
CostsTabela de materiais ou PathfindingModifiers definidos e seu custo para travessia. Útil para fazer com que o agente prefira certos materiais/regiões em vez de outros. Veja modificadores para detalhes.tabelanil

Computação de Caminhos

Após você ter criado um caminho válido com CreatePath(), ele deve ser computado chamando Path:ComputeAsync() com um Vector3 tanto para o ponto de partida quanto para o destino (

LINHAS 133–139
).

Caminho de início/fim marcado através de duas pontes

Uma vez que o Path é computado, ele conterá uma série de waypoints que traçam o caminho do início ao fim. Esses pontos podem ser obtidos com o método Path:GetWaypoints() (

LINHA 142
). O array retornado está disposto na ordem dos waypoints do início do caminho até o fim do caminho.

Waypoints indicados ao longo do caminho computado
Waypoints indicados ao longo do caminho computado

Movimento de Caminhos

Cada PathWaypoint consiste em uma Position (Vector3) e uma Action (PathWaypointAction). Para mover um personagem contendo um Humanoid, como um personagem típico do Roblox, a melhor maneira é chamar Humanoid:Move() de waypoint em waypoint e usar a função de callback isCurrentWaypointReached() do script (

LINHAS 32–56
) para detectar quando o personagem atinge cada waypoint.

Caminhos Bloqueados

Muitos mundos do Roblox são dinâmicos; partes podem se mover ou cair e pisos podem colapsar. Isso pode bloquear um caminho computado e impedir que o personagem alcance seu destino. Para lidar com isso, você pode conectar o evento Path.Blocked e recomputar o caminho ao redor do que o bloqueou (

LINHAS 145–154
).

Modificadores de Busca de Caminhos

Por padrão, Path:ComputeAsync() retorna o caminho mais curto entre o ponto de partida e o destino, com a exceção de que tenta evitar saltos. Isso parece unnatural em algumas situações; por exemplo, um caminho pode passar por água lamacenta em vez de contorná-la simplesmente porque o caminho pela água é geometricamente mais curto.

Dois caminhos indicados com o caminho mais curto não necessariamente mais lógico

Para otimizar a busca de caminhos ainda mais, você pode implementar modificadores de busca de caminhos para computar caminhos mais inteligentes através de vários materiais, ao redor de regiões definidas, ou para ignorar obstáculos.

Custos de Materiais

Ao trabalhar com materiais de Terrain e BasePart, você pode incluir uma tabela de Costs dentro de CreatePath() para tornar certos materiais mais transponíveis do que outros. Todos os materiais têm um custo padrão de 1 e qualquer material pode ser definido como não transponível definindo seu valor como math.huge.

As chaves na tabela Costs devem ser nomes string representando nomes de Enum.Material, por exemplo Water para Enum.Material.Water ou CrackedLava para Enum.Material.CrackedLava.

PlayerPathFollow (LocalScript)

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local DESTINATION = Vector3.new(20, 0.5, 20)
local GROUND_WAIT = 0.01
local VELOCITY_MULTIPLIER = 0.0625
local path = PathfindingService:CreatePath({
AgentCanClimb = true,
Costs = {
Water = 20, CrackedLava = 100, Slate = 20
}
})

Configurar Regiões

Em alguns casos, preferência de material não é suficiente. Por exemplo, você pode querer que os personagens evitem uma região definida, independentemente dos materiais embaixo. Isso pode ser alcançado adicionando um objeto PathfindingModifier a uma parte.

  1. Crie uma parte Anchored ao redor da região e defina sua propriedade CanCollide como false.

    Parte ancorada definindo uma região para aplicar um modificador de busca de caminhos.
  2. Insira uma instância PathfindingModifier na parte, localize sua propriedade Label, e atribua um nome significativo como DangerZone.

    Instância PathfindingModifier com a propriedade Label definida como DangerZone.
  3. Inclua uma chave correspondente DangerZone e um valor numérico associado dentro da tabela Costs de CreatePath(). Um modificador pode ser definido como não transponível definindo seu valor como math.huge.

    PlayerPathFollow (LocalScript)

    local PathfindingService = game:GetService("PathfindingService")
    local Players = game:GetService("Players")
    local RunService = game:GetService("RunService")
    local DESTINATION = Vector3.new(20, 0.5, 20)
    local GROUND_WAIT = 0.01
    local VELOCITY_MULTIPLIER = 0.0625
    local path = PathfindingService:CreatePath({
    AgentCanClimb = true,
    Costs = {
    DangerZone = math.huge, Water = 20, CrackedLava = 20, Slate = 20
    }
    })

Ignorar Obstáculos

Em alguns casos, é útil buscar caminhos através de obstáculos sólidos como se eles não existissem. Isso permite que você compute um caminho através de bloqueadores físicos específicos, em vez de a computação falhar completamente.

  1. Crie uma parte Anchored ao redor do objeto e defina sua propriedade CanCollide como false.

    Parte ancorada definindo uma região para aplicar um modificador de busca de caminhos.
  2. Insira uma instância PathfindingModifier na parte e habilite sua propriedade PassThrough.

    Instância PathfindingModifier com a propriedade PassThrough habilitada.

    Agora, quando um caminho é computado do NPC zumbi para o personagem jogador, o caminho se estende além da porta e você pode forçar o zumbi a atravessá-la. Mesmo que o zumbi não consiga abrir a porta, ele reage como se "ouvisse" o personagem atrás da porta.

    Caminho do NPC zumbi passando pela porta que antes bloqueava.

Às vezes é necessário encontrar um caminho através de um espaço que não pode ser normalmente transposto, como através de um abismo, e realizar uma ação personalizada para alcançar o próximo waypoint. Isso pode ser alcançado através do objeto PathfindingLink.

Usando o exemplo de cima, você pode fazer o agente usar um barco.

PathfindingLink mostrando como um agente pode usar um barco.

Para criar um PathfindingLink usando este exemplo:

  1. OPCIONAL
    Ative Links de Busca de Caminhos no widget de Opções de Visualização no canto superior direito da viewport 3D. Isso ajuda com visualização e depuração ao implementar links de busca de caminhos.

  2. Crie dois Attachments, um no assento do barco e um próximo ao ponto de desembarque do barco.

    Anexos criados para o início e o fim do link de busca de caminhos.
  3. Crie um objeto PathfindingLink no espaço de trabalho e, em seguida, atribua suas propriedades Attachment0 e Attachment1 aos anexos de início e fim, respectivamente.

    Propriedades Attachment0/Attachment1 de um PathfindingLink. PathfindingLink visualizado no mundo 3D.
  4. Atribua um nome significativo como UseBoat à sua propriedade Label. Este nome é usado como um indicador no script de busca de caminhos para acionar uma ação personalizada quando o agente atinge o ponto de início do link.

    Propriedade Label especificada para PathfindingLink.
  5. Inclua uma tabela de Costs dentro de CreatePath() contendo tanto uma chave Water quanto uma chave personalizada correspondente ao nome da propriedade Label. Atribua à chave personalizada um valor menor do que o de Water.

    PlayerPathFollow (LocalScript)

    local PathfindingService = game:GetService("PathfindingService")
    local Players = game:GetService("Players")
    local RunService = game:GetService("RunService")
    local DESTINATION = Vector3.new(20, 0.5, 20)
    local GROUND_WAIT = 0.01
    local VELOCITY_MULTIPLIER = 0.0625
    local path = PathfindingService:CreatePath({
    AgentCanClimb = true,
    Costs = {
    UseBoat = 2, Water = 20
    }
    })
  6. Na função moveToNextWaypoint() (

    LINHAS 93–114
    ), uma verificação personalizada para o nome do modificador Label pode ser usada para executar uma ação diferente de Humanoid:Move(); neste caso, você pode chamar uma função para assentar o agente no barco, mover o barco através da água, soltar o agente no ponto de desembarque do barco, e então continuar o caminho do agente até seu destino final.

Compatibilidade de Streaming

O streaming de instâncias no jogo é um recurso poderoso que carrega e descarrega dinamicamente conteúdo 3D conforme o personagem de um jogador se move pelo mundo. À medida que exploram o espaço 3D, novos subconjuntos do espaço são transmitidos para seu dispositivo e alguns dos subconjuntos existentes podem ser descarregados.

Considere as seguintes melhores práticas para usar PathfindingService em jogos com streaming habilitado:

  • O streaming pode bloquear ou desbloquear um caminho dado conforme um personagem se move ao longo dele. Por exemplo, enquanto um personagem corre pela floresta, uma árvore pode ser transmitida de algum lugar à frente deles e obstruir o caminho. Para fazer a busca de caminhos funcionar perfeitamente com o streaming, é altamente recomendável que você use a técnica de lidar com caminhos bloqueados e recompute o caminho quando necessário.

  • Uma abordagem comum na busca de caminhos é usar as coordenadas de objetos existentes para computação, como definir um destino de caminho na posição de um modelo TreasureChest existente no mundo. Esta abordagem é totalmente compatível com Scripts do lado do servidor, uma vez que o servidor tem visão completa do mundo o tempo todo, mas LocalScripts e ModuleScripts que são executados no cliente podem falhar se tentarem computar um caminho para um objeto que não foi transmitido.

    Para abordar esse problema, considere definir o destino na posição de uma BasePart dentro de um modelo persistente. Modelos persistentes carregam logo após o jogador entrar e nunca são descarregados, então um script do lado do cliente pode se conectar ao evento PersistentLoaded e acessar com segurança o modelo para criar waypoints após o evento ser disparado.

Limitações e Fatores de Falha

O mecanismo de busca de caminhos inclui limitações específicas para garantir processamento eficiente e desempenho ótimo. Além disso, as computações de busca de caminhos podem falhar por vários motivos, conforme descrito abaixo.

©2026 Roblox Corporation, Roblox, o logotipo Roblox e Powering Imagination estão entre nossas marcas registradas e não registradas nos EUA e em outros países.