Búsqueda de rutas

*Este contenido se traduce usando la IA (Beta) y puede contener errores. Para ver esta página en inglés, haz clic en aquí.

Búsqueda de rutas es el proceso de mover un personaje u objeto (agente) a lo largo de un camino lógico alrededor de obstáculos para alcanzar un destino, evitando opcionalmente materiales peligrosos o regiones definidas.

Visualización de navegación

Para ayudar con el diseño y la depuración de la búsqueda de rutas, Studio puede renderizar una malla de navegación y etiquetas de modificadores. Para habilitarlos, activa Malla de navegación y Modificadores de búsqueda de rutas desde el widget de Opciones de visualización en la esquina superior derecha de la vista 3D.

Vista cercana de la vista 3D con el botón de Opciones de Visualización indicado en la esquina superior derecha.

Con Malla de navegación habilitada, las áreas coloreadas muestran dónde un personaje podría caminar o nadar. Flechas pequeñas indican áreas que un personaje intentará alcanzar saltando.

Malla de navegación mostrando en Studio

Implementación

Aunque la búsqueda de rutas se puede implementar de varias maneras a través de PathfindingService y sus métodos asociados como CreatePath(), esta sección utiliza el siguiente script de búsqueda de rutas para el personaje del jugador.

Para probar mientras lees:

  1. IMPORTANTE
    En el Explorador, selecciona el contenedor StarterPlayer. Luego, en la ventana de Propiedades, establece tanto DevComputerMovementMode como DevTouchMovementMode en Scriptable.

  2. Copia el siguiente código en un LocalScript dentro de StarterCharacterScripts, o obtén este paquete y colócalo en StarterCharacterScripts.

    PlayerPathFollow (LocalScript in StarterCharacterScripts)

    local PathfindingService = game:GetService("PathfindingService")
    local Players = game:GetService("Players")
    local RunService = game:GetService("RunService")
    local DESTINO = Vector3.new(20, 0.5, 20)
    local ESPERA_SUELO = 0.01
    local MULTIPLICADOR_VELOCIDAD = 0.0625
    local ruta = PathfindingService:CreatePath({
    AgentCanClimb = true,
    Costs = {
    Water = 20
    }
    })
    local personaje = script.Parent
    local humanoide = personaje:WaitForChild("Humanoid")
    local puntosDeRuta
    local siguienteIndiceDeRuta
    local conexionBloqueada
    local conexionPuntoDeRutaAlcanzado
    local normalPlanoPuntoDeRutaActual = Vector3.zero
    local distanciaPlanoPuntoDeRutaActual = 0
    local buscadorFuncionando = false
    local function desconectarConexionPuntoDeRutaActualAlcanzado()
    if not conexionPuntoDeRutaAlcanzado then return end
    conexionPuntoDeRutaActualAlcanzado:Disconnect()
    conexionPuntoDeRutaActualAlcanzado = nil
    end
    local function esPuntoDeRutaAlcanzado()
    if humanoide.FloorMaterial == Enum.Material.Air then
    return false
    end
    local alcanzado = false
    if normalPlanoPuntoDeRutaActual ~= Vector3.zero then
    -- Computar la distancia del humanoide al plano de destino
    local dist = normalPlanoPuntoDeRutaActual:Dot(humanoide.RootPart.Position) - distanciaPlanoPuntoDeRutaActual
    -- Computar el componente de la velocidad del humanoide que va hacia el plano
    local velocidad = -normalPlanoPuntoDeRutaActual:Dot(humanoide.RootPart.Velocity)
    -- Computar el umbral desde el plano de destino basado en la velocidad del humanoide
    local umbral = math.max(1.0, MULTIPLICADOR_VELOCIDAD * velocidad)
    -- Considerar punto de ruta alcanzado si está menos que el umbral frente al plano
    alcanzado = dist < umbral
    else
    alcanzado = true
    end
    if alcanzado then
    normalPlanoPuntoDeRutaActual = Vector3.zero
    distanciaPlanoPuntoDeRutaActual = 0
    moverAlSiguientePuntoDeRuta()
    end
    end
    local function calcularEnfoqueSiguientePuntoDeRuta()
    siguienteIndiceDeRuta += 1
    if siguienteIndiceDeRuta > #puntosDeRuta then
    return false
    end
    local puntoDeRutaActual = puntosDeRuta[siguienteIndiceDeRuta - 1]
    local siguientePuntoDeRuta = puntosDeRuta[siguienteIndiceDeRuta]
    -- Construir plano de destino desde el siguiente punto de ruta hacia el actual
    normalPlanoPuntoDeRutaActual = puntoDeRutaActual.Position - siguientePuntoDeRuta.Position
    -- Establecer normal perpendicular al plano Y cuando no se sube
    if siguientePuntoDeRuta.Label ~= "Climb" then
    normalPlanoPuntoDeRutaActual = Vector3.new(normalPlanoPuntoDeRutaActual.X, 0, normalPlanoPuntoDeRutaActual.Z)
    end
    if normalPlanoPuntoDeRutaActual.Magnitude > 0.000001 then
    normalPlanoPuntoDeRutaActual = normalPlanoPuntoDeRutaActual.Unit
    distanciaPlanoPuntoDeRutaActual = normalPlanoPuntoDeRutaActual:Dot(siguientePuntoDeRuta.Position)
    end
    return true
    end
    local function reiniciarDatosPuntoDeRuta()
    humanoide:Move(Vector3.zero)
    normalPlanoPuntoDeRutaActual = Vector3.zero
    distanciaPlanoPuntoDeRutaActual = 0
    desconectarConexionPuntoDeRutaActualAlcanzado()
    buscadorFuncionando = false
    end
    local function esperarPorSuelo()
    while humanoide.FloorMaterial == Enum.Material.Air do
    task.wait(ESPERA_SUELO)
    end
    end
    function moverAlSiguientePuntoDeRuta()
    if calcularEnfoqueSiguientePuntoDeRuta() then
    desconectarConexionPuntoDeRutaActualAlcanzado()
    conexionPuntoDeRutaActualAlcanzado = RunService.Heartbeat:Connect(esPuntoDeRutaAlcanzado)
    local siguientePosicionPuntoDeRuta = puntosDeRuta[siguienteIndiceDeRuta].Position
    local siguienteAccionPuntoDeRuta = puntosDeRuta[siguienteIndiceDeRuta].Action
    humanoide:Move(siguientePosicionPuntoDeRuta - humanoide.RootPart.Position)
    if puntosDeRuta[siguienteIndiceDeRuta + 1] and puntosDeRuta[siguienteIndiceDeRuta + 1].Label == "UseBoat" then
    siguienteIndiceDeRuta += 1
    -- Llama a tu propia función personalizada para que el agente use el bote
    elseif siguienteAccionPuntoDeRuta == Enum.PathWaypointAction.Jump then
    humanoide:ChangeState(Enum.HumanoidStateType.Jumping)
    while humanoide.FloorMaterial ~= Enum.Material.Air do
    task.wait(ESPERA_SUELO)
    end
    humanoide:Move(siguientePosicionPuntoDeRuta - humanoide.RootPart.Position)
    end
    else
    reiniciarDatosPuntoDeRuta()
    end
    end
    local function encontrarPuntoDeInicio(puntosDeRuta)
    siguienteIndiceDeRuta = 1
    while siguienteIndiceDeRuta + 1 <= #puntosDeRuta do
    local dist = puntosDeRuta[siguienteIndiceDeRuta + 1].Position - humanoide.RootPart.Position
    dist = Vector3.new(dist.X, 0, dist.Z)
    if dist.magnitude >= 2 then
    return
    end
    siguienteIndiceDeRuta += 1
    end
    end
    local function seguirRuta()
    -- Computar la ruta
    buscadorFuncionando = true
    esperarPorSuelo()
    local exito, mensajeError = pcall(function()
    ruta:ComputeAsync(personaje.PrimaryPart.Position, DESTINO)
    end)
    if not exito or ruta.Status ~= Enum.PathStatus.Success then
    warn("¡Ruta no computada!", mensajeError)
    return
    end
    -- Obtener los puntos de ruta
    puntosDeRuta = ruta:GetWaypoints()
    -- Detectar si la ruta se bloquea
    conexionBloqueada = ruta.Blocked:Connect(function(indicePuntoBloqueado)
    -- Comprobar si el obstáculo está más adelante en el camino
    if indicePuntoBloqueado >= siguienteIndiceDeRuta then
    -- Dejar de detectar el bloqueo de la ruta hasta que se vuelva a computar
    conexionBloqueada:Disconnect()
    reiniciarDatosPuntoDeRuta()
    -- Llama a la función para re-computar la nueva ruta
    seguirRuta()
    end
    end)
    encontrarPuntoDeInicio(puntosDeRuta)
    moverAlSiguientePuntoDeRuta()
    end
    seguirRuta()
  3. Edita la variable DESTINO (

    LÍNEA 5
    ) a un destino de Vector3 dentro del mundo 3D que el personaje jugador pueda alcanzar.

  4. Continúa a través de las siguientes secciones para aprender sobre la computación de rutas y el movimiento de personajes.

Creación de rutas

La búsqueda de rutas se inicia a través de PathfindingService y su método CreatePath() (

LÍNEAS 9–14
). Este método acepta una tabla opcional de parámetros que ajustan cómo se mueve el personaje (agente) a lo largo de la ruta.

ClaveDescripciónTipoPredeterminado
AgentRadiusRadio del agente, en studs. Útil para determinar la separación mínima de obstáculos.entero2
AgentHeightAltura del agente, en studs. El espacio vacío más pequeño que este valor, como el espacio bajo las escaleras, será marcado como no transitable.entero5
AgentCanJumpDetermina si se permite saltar durante la búsqueda de rutas.booleanotrue
AgentCanClimbDetermina si se permite escalar TrussParts durante la búsqueda de rutas. Un camino escalable tiene una Label llamada Climb y el costo por un camino escalable es 1 por defecto.booleanofalse
WaypointSpacingEspaciado entre puntos intermedios en la ruta. Si se establece en math.huge, no habrá puntos intermedios.número4
CostsTabla de materiales o PathfindingModifiers definidos y su costo de traversibilidad. Útil para hacer que el agente prefiera ciertos materiales/regiones sobre otros. Ver modificadores para más detalles.tablanil

Cómputo de rutas

Después de que hayas creado una ruta válida con CreatePath(), debe ser computada llamando a Path:ComputeAsync() con un Vector3 tanto para el punto de inicio como para el destino (

LÍNEAS 133–139
).

Inicio/final de la ruta marcado a través de dos puentes

Una vez que el Path es computado, contendrá una serie de puntos de ruta que trazan el camino desde el inicio hasta el final. Estos puntos se pueden reunir con el método Path:GetWaypoints() (

LÍNEA 142
). El arreglo devuelto está organizado en el orden de los puntos de ruta desde el inicio de la ruta hasta el final de la ruta.

Puntos de ruta indicados a lo largo de la ruta computada
Puntos de ruta indicados a lo largo de la ruta computada

Movimiento de rutas

Cada PathWaypoint consiste tanto en una Position (Vector3) como en una Action (PathWaypointAction). Para mover un personaje que contenga un Humanoid, como un personaje típico de Roblox, la mejor manera es llamar a Humanoid:Move() de punto de ruta a punto de ruta y usar el callback isCurrentWaypointReached() del script (

LÍNEAS 32–56
) para detectar cuándo el personaje alcanza cada punto de ruta.

Rutas bloqueadas

Muchos mundos de Roblox son dinámicos; las piezas pueden moverse o caer y los pisos pueden colapsar. Esto puede bloquear un camino computado e impedir que el personaje alcance su destino. Para manejar esto, puedes conectar el evento Path.Blocked y recomputar la ruta alrededor de lo que lo bloqueó (

LÍNEAS 145–154
).

Modificadores de búsqueda de rutas

Por defecto, Path:ComputeAsync() devuelve la ruta más corta entre el punto de inicio y el destino, con la excepción de que intenta evitar saltos. Esto puede parecer poco natural en algunas situaciones; por ejemplo, una ruta puede pasar por agua pantanosa en lugar de alrededor de ella simplemente porque el camino a través del agua es geométricamente más corto.

Dos rutas indicadas con la ruta más corta no necesariamente más lógica

Para optimizar aún más la búsqueda de rutas, puedes implementar modificadores de búsqueda de rutas para calcular rutas más inteligentes a través de varios materiales, alrededor de regiones definidas o para ignorar obstáculos.

Costos de materiales

Al trabajar con Terrain y materiales de BasePart, puedes incluir una tabla de Costs dentro de CreatePath() para hacer ciertos materiales más transitables que otros. Todos los materiales tienen un costo predeterminado de 1 y cualquier material puede definirse como no transitables estableciendo su valor en math.huge.

Las claves en la tabla Costs deben ser nombres de cadena que representen nombres de Enum.Material, por ejemplo Water para Enum.Material.Water o CrackedLava para Enum.Material.CrackedLava.

PlayerPathFollow (LocalScript)

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local DESTINO = Vector3.new(20, 0.5, 20)
local ESPERA_SUELO = 0.01
local MULTIPLICADOR_VELOCIDAD = 0.0625
local ruta = PathfindingService:CreatePath({
AgentCanClimb = true,
Costs = {
Water = 20, CrackedLava = 100, Slate = 20
}
})

Configurar regiones

En algunos casos, la preferencia de materiales no es suficiente. Por ejemplo, es posible que desees que los personajes eviten una región definida, independientemente de los materiales bajo sus pies. Esto se puede lograr agregando un objeto PathfindingModifier a una parte.

  1. Crea una parte Anchored alrededor de la región y establece su propiedad CanCollide en false.

    Parte anclada definiendo una región a la que aplicar un modificador de búsqueda de rutas.
  2. Inserta una instancia de PathfindingModifier en la parte, ubica su propiedad Label y asígnale un nombre significativo como DangerZone.

    Instancia de PathfindingModifier con la propiedad Label ajustada a DangerZone.
  3. Incluye una clave DangerZone coincidente y un valor numérico asociado dentro de la tabla Costs de CreatePath(). Un modificador puede definirse como no transitable estableciendo su valor en math.huge.

    PlayerPathFollow (LocalScript)

    local PathfindingService = game:GetService("PathfindingService")
    local Players = game:GetService("Players")
    local RunService = game:GetService("RunService")
    local DESTINO = Vector3.new(20, 0.5, 20)
    local ESPERA_SUELO = 0.01
    local MULTIPLICADOR_VELOCIDAD = 0.0625
    local ruta = PathfindingService:CreatePath({
    AgentCanClimb = true,
    Costs = {
    DangerZone = math.huge, Water = 20, CrackedLava = 20, Slate = 20
    }
    })

Ignorar obstáculos

En algunos casos, es útil buscar una ruta a través de obstáculos sólidos como si no existieran. Esto te permite computar un camino a través de bloqueadores físicos específicos, en lugar de que la computación falle por completo.

  1. Crea una parte Anchored alrededor del objeto y establece su propiedad CanCollide en false.

    Parte anclada definiendo una región a la que aplicar un modificador de búsqueda de rutas.
  2. Inserta una instancia de PathfindingModifier en la parte y habilita su propiedad PassThrough.

    Instancia de PathfindingModifier con la propiedad PassThrough habilitada.

    Ahora, cuando se computa una ruta desde el NPC zombie hasta el personaje jugador, la ruta se extiende más allá de la puerta y puedes indicarle al zombie que la atraviese. Incluso si el zombie no puede abrir la puerta, reacciona como si "oyera" al personaje detrás de la puerta.

    Ruta del NPC zombie pasando a través de la puerta que bloqueaba anteriormente.

Enlaces de búsqueda de rutas

A veces es necesario encontrar un camino a través de un espacio que no se puede atravesar normalmente, como a través de un abismo, y realizar una acción personalizada para alcanzar el siguiente punto de ruta. Esto se puede lograr a través del objeto PathfindingLink.

Usando el ejemplo de arriba, puedes hacer que el agente use un bote.

PathfindingLink mostrando cómo un agente puede usar un bote.

Para crear un PathfindingLink usando este ejemplo:

  1. OPCIONAL
    Activa Enlaces de búsqueda de rutas desde el widget de Opciones de visualización en la esquina superior derecha de la vista 3D. Esto ayuda con la visualización y la depuración al implementar enlaces de búsqueda de rutas.

  2. Crea dos Attachments, uno en el asiento del bote y otro cerca del punto de aterrizaje del bote.

    Adjuntos creados para el inicio y el final del enlace de búsqueda.
  3. Crea un objeto PathfindingLink en el espacio de trabajo, luego asigna sus propiedades Attachment0 y Attachment1 a los adjuntos de inicio y de final respectivamente.

    Propiedades Attachment0/Attachment1 de un PathfindingLink. PathfindingLink visualizado en el mundo 3D.
  4. Asigna un nombre significativo como UseBoat a su propiedad Label. Este nombre se usa como una bandera en el script de búsqueda de rutas para activar una acción personalizada cuando el agente alcanza el punto de enlace de inicio.

    Propiedad Label especificada para PathfindingLink.
  5. Incluye una tabla de Costs dentro de CreatePath() que contenga tanto una clave Water como una clave personalizada que coincida con el nombre de la propiedad Label. Asigna a la clave personalizada un valor inferior al de Water.

    PlayerPathFollow (LocalScript)

    local PathfindingService = game:GetService("PathfindingService")
    local Players = game:GetService("Players")
    local RunService = game:GetService("RunService")
    local DESTINO = Vector3.new(20, 0.5, 20)
    local ESPERA_SUELO = 0.01
    local MULTIPLICADOR_VELOCIDAD = 0.0625
    local ruta = PathfindingService:CreatePath({
    AgentCanClimb = true,
    Costs = {
    UseBoat = 2, Water = 20
    }
    })
  6. En la función moverAlSiguientePuntoDeRuta() (

    LÍNEAS 93–114
    ), se puede usar una comprobación personalizada para el nombre del modificador Label para realizar una acción diferente a Humanoid:Move(); en este caso, podrías llamar a una función para sentar al agente en el bote, mover el bote a través del agua, desasentar al agente en el punto de aterrizaje del bote y luego continuar el camino del agente hacia su destino final.

Compatibilidad con streaming

La transmisión de instancias en el juego es una poderosa característica que carga y descarga dinámicamente contenido 3D a medida que el personaje del jugador se mueve por el mundo. A medida que exploran el espacio 3D, nuevos subconjuntos del espacio se transmiten a su dispositivo y algunos de los subconjuntos existentes pueden ser descargados.

Considera las siguientes mejores prácticas para usar PathfindingService en juegos habilitados para streaming:

  • La transmisión puede bloquear o desbloquear un camino dado a medida que un personaje se mueve a lo largo de él. Por ejemplo, mientras un personaje corre a través de un bosque, un árbol podría transmitirse desde algún lugar delante de ellos y obstruir el camino. Para hacer que la búsqueda de rutas funcione sin problemas con la transmisión, se recomienda encarecidamente usar la técnica de manejo de rutas bloqueadas y recomputar la ruta cuando sea necesario.

  • Un enfoque común en la búsqueda de rutas es usar las coordenadas de objetos existentes para la computación, como establecer un destino de ruta en la posición de un modelo existente de TreasureChest en el mundo. Este enfoque es completamente compatible con Scripts del lado del servidor, ya que el servidor tiene visión completa del mundo en todo momento, pero los LocalScripts y ModuleScripts que se ejecutan en el cliente pueden fallar si intentan computar una ruta hacia un objeto que no se ha transmitido.

    Para abordar este problema, considera establecer el destino en la posición de una BasePart dentro de un modelo persistente. Los modelos persistentes se cargan poco después de que el jugador se une y nunca se transmiten, por lo que un script del lado del cliente puede conectarse al evento PersistentLoaded y acceder de forma segura al modelo para crear puntos de ruta después de que se dispare el evento.

Limitaciones y factores de falla

El motor de búsqueda de rutas incluye limitaciones específicas para asegurar un procesamiento eficiente y un rendimiento óptimo. Además, los cálculos de búsqueda de rutas pueden fallar por varias razones como se describe a continuación.

©2026 Roblox Corporation. Roblox, el logotipo de Roblox y "Powering Imagination" son algunas de nuestras marcas registradas y no registradas en los Estados Unidos y otros países.