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.

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.

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:
- IMPORTANTEEn el Explorador, selecciona el contenedor StarterPlayer. Luego, en la ventana de Propiedades, establece tanto DevComputerMovementMode como DevTouchMovementMode en Scriptable.


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.01local MULTIPLICADOR_VELOCIDAD = 0.0625local ruta = PathfindingService:CreatePath({AgentCanClimb = true,Costs = {Water = 20}})local personaje = script.Parentlocal humanoide = personaje:WaitForChild("Humanoid")local puntosDeRutalocal siguienteIndiceDeRutalocal conexionBloqueadalocal conexionPuntoDeRutaAlcanzadolocal normalPlanoPuntoDeRutaActual = Vector3.zerolocal distanciaPlanoPuntoDeRutaActual = 0local buscadorFuncionando = falselocal function desconectarConexionPuntoDeRutaActualAlcanzado()if not conexionPuntoDeRutaAlcanzado then return endconexionPuntoDeRutaActualAlcanzado:Disconnect()conexionPuntoDeRutaActualAlcanzado = nilendlocal function esPuntoDeRutaAlcanzado()if humanoide.FloorMaterial == Enum.Material.Air thenreturn falseendlocal alcanzado = falseif normalPlanoPuntoDeRutaActual ~= Vector3.zero then-- Computar la distancia del humanoide al plano de destinolocal dist = normalPlanoPuntoDeRutaActual:Dot(humanoide.RootPart.Position) - distanciaPlanoPuntoDeRutaActual-- Computar el componente de la velocidad del humanoide que va hacia el planolocal velocidad = -normalPlanoPuntoDeRutaActual:Dot(humanoide.RootPart.Velocity)-- Computar el umbral desde el plano de destino basado en la velocidad del humanoidelocal umbral = math.max(1.0, MULTIPLICADOR_VELOCIDAD * velocidad)-- Considerar punto de ruta alcanzado si está menos que el umbral frente al planoalcanzado = dist < umbralelsealcanzado = trueendif alcanzado thennormalPlanoPuntoDeRutaActual = Vector3.zerodistanciaPlanoPuntoDeRutaActual = 0moverAlSiguientePuntoDeRuta()endendlocal function calcularEnfoqueSiguientePuntoDeRuta()siguienteIndiceDeRuta += 1if siguienteIndiceDeRuta > #puntosDeRuta thenreturn falseendlocal puntoDeRutaActual = puntosDeRuta[siguienteIndiceDeRuta - 1]local siguientePuntoDeRuta = puntosDeRuta[siguienteIndiceDeRuta]-- Construir plano de destino desde el siguiente punto de ruta hacia el actualnormalPlanoPuntoDeRutaActual = puntoDeRutaActual.Position - siguientePuntoDeRuta.Position-- Establecer normal perpendicular al plano Y cuando no se subeif siguientePuntoDeRuta.Label ~= "Climb" thennormalPlanoPuntoDeRutaActual = Vector3.new(normalPlanoPuntoDeRutaActual.X, 0, normalPlanoPuntoDeRutaActual.Z)endif normalPlanoPuntoDeRutaActual.Magnitude > 0.000001 thennormalPlanoPuntoDeRutaActual = normalPlanoPuntoDeRutaActual.UnitdistanciaPlanoPuntoDeRutaActual = normalPlanoPuntoDeRutaActual:Dot(siguientePuntoDeRuta.Position)endreturn trueendlocal function reiniciarDatosPuntoDeRuta()humanoide:Move(Vector3.zero)normalPlanoPuntoDeRutaActual = Vector3.zerodistanciaPlanoPuntoDeRutaActual = 0desconectarConexionPuntoDeRutaActualAlcanzado()buscadorFuncionando = falseendlocal function esperarPorSuelo()while humanoide.FloorMaterial == Enum.Material.Air dotask.wait(ESPERA_SUELO)endendfunction moverAlSiguientePuntoDeRuta()if calcularEnfoqueSiguientePuntoDeRuta() thendesconectarConexionPuntoDeRutaActualAlcanzado()conexionPuntoDeRutaActualAlcanzado = RunService.Heartbeat:Connect(esPuntoDeRutaAlcanzado)local siguientePosicionPuntoDeRuta = puntosDeRuta[siguienteIndiceDeRuta].Positionlocal siguienteAccionPuntoDeRuta = puntosDeRuta[siguienteIndiceDeRuta].Actionhumanoide:Move(siguientePosicionPuntoDeRuta - humanoide.RootPart.Position)if puntosDeRuta[siguienteIndiceDeRuta + 1] and puntosDeRuta[siguienteIndiceDeRuta + 1].Label == "UseBoat" thensiguienteIndiceDeRuta += 1-- Llama a tu propia función personalizada para que el agente use el boteelseif siguienteAccionPuntoDeRuta == Enum.PathWaypointAction.Jump thenhumanoide:ChangeState(Enum.HumanoidStateType.Jumping)while humanoide.FloorMaterial ~= Enum.Material.Air dotask.wait(ESPERA_SUELO)endhumanoide:Move(siguientePosicionPuntoDeRuta - humanoide.RootPart.Position)endelsereiniciarDatosPuntoDeRuta()endendlocal function encontrarPuntoDeInicio(puntosDeRuta)siguienteIndiceDeRuta = 1while siguienteIndiceDeRuta + 1 <= #puntosDeRuta dolocal dist = puntosDeRuta[siguienteIndiceDeRuta + 1].Position - humanoide.RootPart.Positiondist = Vector3.new(dist.X, 0, dist.Z)if dist.magnitude >= 2 thenreturnendsiguienteIndiceDeRuta += 1endendlocal function seguirRuta()-- Computar la rutabuscadorFuncionando = trueesperarPorSuelo()local exito, mensajeError = pcall(function()ruta:ComputeAsync(personaje.PrimaryPart.Position, DESTINO)end)if not exito or ruta.Status ~= Enum.PathStatus.Success thenwarn("¡Ruta no computada!", mensajeError)returnend-- Obtener los puntos de rutapuntosDeRuta = ruta:GetWaypoints()-- Detectar si la ruta se bloqueaconexionBloqueada = ruta.Blocked:Connect(function(indicePuntoBloqueado)-- Comprobar si el obstáculo está más adelante en el caminoif indicePuntoBloqueado >= siguienteIndiceDeRuta then-- Dejar de detectar el bloqueo de la ruta hasta que se vuelva a computarconexionBloqueada:Disconnect()reiniciarDatosPuntoDeRuta()-- Llama a la función para re-computar la nueva rutaseguirRuta()endend)encontrarPuntoDeInicio(puntosDeRuta)moverAlSiguientePuntoDeRuta()endseguirRuta()Edita la variable DESTINO (
LÍNEA 5) a un destino de Vector3 dentro del mundo 3D que el personaje jugador pueda alcanzar.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() (
| Clave | Descripción | Tipo | Predeterminado |
|---|---|---|---|
| AgentRadius | Radio del agente, en studs. Útil para determinar la separación mínima de obstáculos. | entero | 2 |
| AgentHeight | Altura 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. | entero | 5 |
| AgentCanJump | Determina si se permite saltar durante la búsqueda de rutas. | booleano | true |
| AgentCanClimb | Determina 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. | booleano | false |
| WaypointSpacing | Espaciado entre puntos intermedios en la ruta. Si se establece en math.huge, no habrá puntos intermedios. | número | 4 |
| Costs | Tabla 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. | tabla | nil |
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 (

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() (

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 (
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ó (
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.

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.01local MULTIPLICADOR_VELOCIDAD = 0.0625local 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.
Crea una parte Anchored alrededor de la región y establece su propiedad CanCollide en false.

Inserta una instancia de PathfindingModifier en la parte, ubica su propiedad Label y asígnale un nombre significativo como DangerZone.

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.01local MULTIPLICADOR_VELOCIDAD = 0.0625local 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.
Crea una parte Anchored alrededor del objeto y establece su propiedad CanCollide en false.

Inserta una instancia de PathfindingModifier en la parte y habilita su propiedad PassThrough.

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.

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.

Para crear un PathfindingLink usando este ejemplo:
- OPCIONALActiva 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.
Crea dos Attachments, uno en el asiento del bote y otro cerca del punto de aterrizaje del bote.

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.


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.

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.01local MULTIPLICADOR_VELOCIDAD = 0.0625local ruta = PathfindingService:CreatePath({AgentCanClimb = true,Costs = {UseBoat = 2, Water = 20}})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.