Búsqueda de caminos 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 caminos, Studio puede renderizar una malla de navegación y etiquetas de modificadores. Para habilitarlas, activa Malla de navegación y Modificadores de búsqueda de caminos 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 caminos 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 caminos 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 en 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 path = PathfindingService:CreatePath({AgentCanClimb = true,Costs = {Water = 20}})local character = script.Parentlocal humanoid = character:WaitForChild("Humanoid")local waypointslocal nextWaypointIndexlocal blockedConnectionlocal currentWaypointReachedConnectionlocal currentWaypointPlaneNormal = Vector3.zerolocal currentWaypointPlaneDistance = 0local pathfinderWorking = falselocal function disconnectCurrentWaypointReachedConnection()if not currentWaypointReachedConnection then return endcurrentWaypointReachedConnection:Disconnect()currentWaypointReachedConnection = nilendlocal function isCurrentWaypointReached()if humanoid.FloorMaterial == Enum.Material.Air thenreturn falseendlocal reached = falseif currentWaypointPlaneNormal ~= Vector3.zero then-- Calcular la distancia desde el humanoide hasta el plano de destinolocal dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance-- Calcular el componente de la velocidad del humanoide que está hacia el planolocal velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)-- Calcular el umbral desde el plano de destino basado en la velocidad del humanoidelocal threshold = math.max(1.0, MULTIPLICADOR_VELOCIDAD * velocity)-- Considerar el waypoint alcanzado si es menor que el umbral frente al planoreached = dist < thresholdelsereached = trueendif reached thencurrentWaypointPlaneNormal = Vector3.zerocurrentWaypointPlaneDistance = 0moveToNextWaypoint()endendlocal function calculateNextWaypointApproach()nextWaypointIndex += 1if nextWaypointIndex > #waypoints thenreturn falseendlocal currentWaypoint = waypoints[nextWaypointIndex - 1]local nextWaypoint = waypoints[nextWaypointIndex]-- Construir el plano de destino desde el siguiente waypoint hacia el actualcurrentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position-- Establecer la normal perpendicular al plano Y cuando no se está subiendoif nextWaypoint.Label ~= "Climb" thencurrentWaypointPlaneNormal = Vector3.new(currentWaypointPlaneNormal.X, 0, currentWaypointPlaneNormal.Z)endif currentWaypointPlaneNormal.Magnitude > 0.000001 thencurrentWaypointPlaneNormal = currentWaypointPlaneNormal.UnitcurrentWaypointPlaneDistance = currentWaypointPlaneNormal:Dot(nextWaypoint.Position)endreturn trueendlocal function resetWaypointData()humanoid:Move(Vector3.zero)currentWaypointPlaneNormal = Vector3.zerocurrentWaypointPlaneDistance = 0disconnectCurrentWaypointReachedConnection()pathfinderWorking = falseendlocal function waitForGround()while humanoid.FloorMaterial == Enum.Material.Air dotask.wait(ESPERA_SUELO)endendfunction moveToNextWaypoint()if calculateNextWaypointApproach() thendisconnectCurrentWaypointReachedConnection()currentWaypointReachedConnection = RunService.Heartbeat:Connect(isCurrentWaypointReached)local nextWaypointPosition = waypoints[nextWaypointIndex].Positionlocal nextWaypointAction = waypoints[nextWaypointIndex].Actionhumanoid:Move(nextWaypointPosition - humanoid.RootPart.Position)if waypoints[nextWaypointIndex + 1] and waypoints[nextWaypointIndex + 1].Label == "UseBoat" thennextWaypointIndex += 1-- Llama a tu propia función personalizada para hacer que el agente use el barcoelseif nextWaypointAction == Enum.PathWaypointAction.Jump thenhumanoid:ChangeState(Enum.HumanoidStateType.Jumping)while humanoid.FloorMaterial ~= Enum.Material.Air dotask.wait(ESPERA_SUELO)endhumanoid:Move(nextWaypointPosition - humanoid.RootPart.Position)endelseresetWaypointData()endendlocal function findStartingPoint(waypoints)nextWaypointIndex = 1while nextWaypointIndex + 1 <= #waypoints dolocal dist = waypoints[nextWaypointIndex + 1].Position - humanoid.RootPart.Positiondist = Vector3.new(dist.X, 0, dist.Z)if dist.magnitude >= 2 thenreturnendnextWaypointIndex += 1endendlocal function followPath()-- Calcular el caminopathfinderWorking = truewaitForGround()local success, errorMessage = pcall(function()path:ComputeAsync(character.PrimaryPart.Position, DESTINO)end)if not success or path.Status ~= Enum.PathStatus.Success thenwarn("¡Camino no computado!", errorMessage)returnend-- Obtener los waypoints del caminowaypoints = path:GetWaypoints()-- Detectar si el camino se bloqueablockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)-- Verificar si el obstáculo está más adelante en el caminoif blockedWaypointIndex >= nextWaypointIndex then-- Dejar de detectar el bloqueo del camino hasta que se vuelva a calcularblockedConnection:Disconnect()resetWaypointData()-- Llama a la función para volver a calcular un nuevo caminofollowPath()endend)findStartingPoint(waypoints)moveToNextWaypoint()endfollowPath()Edita la variable DESTINO (
LÍNEA 5) a un destino de Vector3 dentro del mundo 3D al que el personaje jugador pueda llegar.Procede a través de las siguientes secciones para aprender sobre la computación de caminos y el movimiento del personaje.
Creación de caminos
La búsqueda de caminos 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 los obstáculos. | entero | 2 |
| AgentHeight | Altura del agente, en studs. El espacio vacío más pequeño que este valor, como el espacio debajo de las escaleras, se marcará como no transitable. | entero | 5 |
| AgentCanJump | Determina si se permite saltar durante la búsqueda de caminos. | booleano | true |
| AgentCanClimb | Determina si se permite escalar TrussParts durante la búsqueda de caminos. Un camino escalable tiene una Label llamada Climb y el costo para un camino escalable es 1 por defecto. | booleano | false |
| WaypointSpacing | Espaciado entre waypoints intermedios en el camino. Si se establece en math.huge, no habrá waypoints intermedios. | número | 4 |
| Costs | Tabla de materiales o PathfindingModifiers definidos y su costo de tránsito. Útil para hacer que el agente prefiera ciertos materiales/regiones sobre otros. Consulta modificadores para más detalles. | tabla | nil |
Computación de caminos
Después de haber creado un camino válido con CreatePath(), debe ser computado llamando a Path:ComputeAsync() con un Vector3 tanto para el punto de partida como para el destino (

Una vez que se computa el Path, contendrá una serie de waypoints que trazan el camino de inicio a fin. Estos puntos se pueden reunir con el método Path:GetWaypoints() (

Movimiento por el camino
Cada PathWaypoint consiste en una Position (Vector3) y 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 waypoint a waypoint y usar el callback isCurrentWaypointReached() del script (
Caminos bloqueados
Muchos mundos de Roblox son dinámicos; las partes pueden moverse o caer y los pisos pueden colapsar. Esto puede bloquear un camino computado y evitar que el personaje alcance su destino. Para manejar esto, puedes conectar el evento Path.Blocked y volver a computar el camino alrededor de lo que lo bloqueó (
Modificadores de búsqueda de caminos
Por defecto, Path:ComputeAsync() devuelve el camino más corto entre el punto de partida y el destino, con la excepción de que intenta evitar saltos. Esto parece antinatural en algunas situaciones; por ejemplo, un camino puede pasar a través de agua pantanosa en lugar de rodearla 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 caminos, puedes implementar modificadores de búsqueda de caminos para calcular caminos más inteligentes a través de varios materiales, alrededor de regiones definidas, o para ignorar obstáculos.
Costos de materiales
Al trabajar con materiales de Terrain y BasePart, puedes incluir una tabla de Costs dentro de CreatePath() para hacer que ciertos materiales sean más transitables que otros. Todos los materiales tienen un costo predeterminado de 1 y cualquier material puede definirse como no transitable estableciendo su valor en math.huge.
Las claves en la tabla Costs deben ser nombres de cadena que representan nombres de Enum.Material, por ejemplo Water para Enum.Material.Water o CrackedLava para Enum.Material.CrackedLava.
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 path = 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, localiza su propiedad Label, y asigna 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 path = PathfindingService:CreatePath({AgentCanClimb = true,Costs = {DangerZone = math.huge, Water = 20, CrackedLava = 20, Slate = 20}})
Ignorar obstáculos
En algunos casos, es útil buscar caminos a través de obstáculos sólidos como si no existieran. Esto te permite calcular 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 compute un camino desde el NPC zombie hasta el personaje jugador, el camino se extiende más allá de la puerta y puedes hacer que el zombie lo 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 caminos
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 waypoint. Esto se puede lograr a través del objeto PathfindingLink.
Usando el ejemplo anterior, puedes hacer que el agente use un barco.

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

Crea un objeto PathfindingLink en el espacio de trabajo, luego asigna sus propiedades Attachment0 y Attachment1 a los adjuntos de inicio y fin respectivamente.


Asigna un nombre significativo como UseBoat a su propiedad Label. Este nombre se utiliza como una bandera en el script de búsqueda de caminos para activar una acción personalizada cuando el agente alcanza el punto de inicio del enlace.

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 menor que el 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 path = PathfindingService:CreatePath({AgentCanClimb = true,Costs = {UseBoat = 2, Water = 20}})En la función moveToNextWaypoint() (
LÍNEAS 93–114), se puede usar una verificació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 barco, mover el barco a través del agua, desasentar al agente en el punto de aterrizaje del barco y luego continuar el camino del agente hacia su destino final.
Compatibilidad con streaming
El streaming de instancias en el juego es una característica poderosa que carga y descarga dinámicamente contenido 3D a medida que el personaje de un 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 transmitirse fuera.
Considera las siguientes mejores prácticas para usar PathfindingService en juegos habilitados para streaming:
El streaming 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 puede transmitirse en algún lugar delante de ellos y obstruir el camino. Para que la búsqueda de caminos funcione sin problemas con el streaming, se recomienda encarecidamente que utilices la técnica de manejo de caminos bloqueados y vuelvas a computar el camino cuando sea necesario.
Un enfoque común en la búsqueda de caminos es usar las coordenadas de objetos existentes para la computación, como establecer un destino de camino en la posición de un modelo TreasureChest existente en el mundo. Este enfoque es completamente compatible con Scripts del lado del servidor, ya que el servidor tiene una vista completa del mundo en todo momento, pero LocalScripts y ModuleScripts que se ejecutan en el cliente pueden fallar si intentan computar un camino 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 fuera, por lo que un script del lado del cliente puede conectarse al evento PersistentLoaded y acceder de manera segura al modelo para crear waypoints después de que se dispare el evento.
Limitaciones y factores de fallo
El motor de búsqueda de caminos incluye limitaciones específicas para garantizar un procesamiento eficiente y un rendimiento óptimo. Además, las computaciones de búsqueda de caminos pueden fallar por varias razones, como se describe a continuación.