Navigazione è il processo di spostamento di un personaggio o oggetto (agente) lungo un percorso logico attorno agli ostacoli per raggiungere una destinazione, evitando eventualmente materiali pericolosi o regioni definite.
Visualizzazione della navigazione
Per assistere con il layout e il debug della navigazione, Studio può rendere una mesh di navigazione e etichette di modificatori. Per abilitarli, attiva Mesh di Navigazione e Modificatori per la Navigazione dal widget delle Opzioni di Visualizzazione nell'angolo in alto a destra della vista 3D.

Con Mesh di Navigazione abilitata, le aree colorate mostrano dove un personaggio potrebbe camminare o nuotare. Piccole frecce indicano le aree che un personaggio tenterà di raggiungere saltando.

Implementazione
Sebbene la navigazione possa essere implementata in vari modi tramite PathfindingService e i suoi metodi associati come CreatePath(), questa sezione utilizza il seguente script di navigazione per il personaggio del giocatore.
Per testare mentre leggi:
- IMPORTANTENel Esplora, seleziona il contenitore StarterPlayer. Quindi, nella finestra Proprietà, imposta sia DevComputerMovementMode che DevTouchMovementMode su Scriptable.


Copia il seguente codice in un LocalScript all'interno di StarterCharacterScripts, oppure ottieni questo pacchetto e depositalo in 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.01local VELOCITY_MULTIPLIER = 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-- Calcola la distanza dal humanoide al piano di destinazionelocal dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance-- Calcola la componente della velocità dell'umanoide che è verso il pianolocal velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)-- Calcola la soglia dal piano di destinazione in base alla velocità dell'umanoidelocal threshold = math.max(1.0, VELOCITY_MULTIPLIER * velocity)-- Considera il waypoint raggiunto se meno della soglia di fronte al pianoreached = 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]-- Costruisci il piano di destinazione dal prossimo waypoint verso quello attualecurrentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position-- Imposta la normale perpendicolare al piano Y quando non si sta salendoif 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(GROUND_WAIT)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-- Chiama la tua funzione personalizzata per far utilizzare la barca all'agenteelseif nextWaypointAction == Enum.PathWaypointAction.Jump thenhumanoid:ChangeState(Enum.HumanoidStateType.Jumping)while humanoid.FloorMaterial ~= Enum.Material.Air dotask.wait(GROUND_WAIT)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()-- Calcola il percorsopathfinderWorking = truewaitForGround()local success, errorMessage = pcall(function()path:ComputeAsync(character.PrimaryPart.Position, DESTINATION)end)if not success or path.Status ~= Enum.PathStatus.Success thenwarn("Percorso non calcolato!", errorMessage)returnend-- Ottieni i waypoint del percorsowaypoints = path:GetWaypoints()-- Rileva se il percorso diventa bloccatoblockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)-- Controlla se l'ostacolo è più avanti lungo il percorsoif blockedWaypointIndex >= nextWaypointIndex then-- Smetti di rilevare il blocco del percorso fino a quando il percorso non è ricalcolatoblockedConnection:Disconnect()resetWaypointData()-- Chiama la funzione per ricalcolare il nuovo percorsofollowPath()endend)findStartingPoint(waypoints)moveToNextWaypoint()endfollowPath()Modifica la variabile DESTINATION (
LINE 5) in una destinazione Vector3 all'interno del mondo 3D a cui il personaggio del giocatore può accedere.Prosegui attraverso le sezioni seguenti per conoscere il calcolo del percorso e il movimento del personaggio.
Creazione del percorso
La navigazione è avviata tramite PathfindingService e il suo metodo CreatePath() (
| Chiave | Descrizione | Tipo | Default |
|---|---|---|---|
| AgentRadius | Raggio dell'agente, in stud. Utile per determinare la separazione minima dagli ostacoli. | intero | 2 |
| AgentHeight | Altezza dell'agente, in stud. Spazi vuoti più piccoli di questo valore, come lo spazio sotto le scale, verranno contrassegnati come non attraversabili. | intero | 5 |
| AgentCanJump | Determina se il salto durante la navigazione è consentito. | booleano | true |
| AgentCanClimb | Determina se la scalata di TrussParts durante la navigazione è consentita. Un percorso scalabile ha un'etichetta di Label chiamata Climb e il costo per un percorso scalabile è 1 per impostazione predefinita. | booleano | false |
| WaypointSpacing | Spaziatura tra i waypoint intermedi nel percorso. Se impostato su math.huge, non ci saranno waypoint intermedi. | numero | 4 |
| Costs | Tabella di materiali o PathfindingModifiers definiti e il loro costo per la traversabilità. Utile per far preferire all'agente determinati materiali/regioni rispetto ad altri. Vedi i modificatori per dettagli. | tabella | nil |
Calcolo del percorso
Dopo aver creato un percorso valido con CreatePath(), deve essere calcolato chiamando Path:ComputeAsync() con un Vector3 sia per il punto di partenza che per la destinazione (

Una volta che il Path è calcolato, conterrà una serie di waypoint che tracciano il percorso dall'inizio alla fine. Questi punti possono essere raccolti con il metodo Path:GetWaypoints() (

Movimento del percorso
Ogni PathWaypoint è composto sia da una Position (Vector3) e Action (PathWaypointAction). Per muovere un personaggio contenente un Humanoid, come un tipico personaggio Roblox, il modo migliore è chiamare Humanoid:Move() da waypoint a waypoint e utilizzare il callback isCurrentWaypointReached() dello script (
Percorsi bloccati
Molti mondi Roblox sono dinamici; le parti potrebbero muoversi o cadere e i pavimenti possono crollare. Questo può bloccare un percorso calcolato e impedire al personaggio di raggiungere la sua destinazione. Per gestire questo, puoi connettere l'evento Path.Blocked e ricalcolare il percorso attorno a ciò che lo blocca (
Modificatori per la navigazione
Per impostazione predefinita, Path:ComputeAsync() restituisce il percorso più corto tra il punto di partenza e la destinazione, con l'eccezione che tenta di evitare salti. Questo appare innaturale in alcune situazioni; ad esempio, un percorso potrebbe attraversare un'acqua paludosa piuttosto che attorno ad essa semplicemente perché il percorso attraverso l'acqua è geometricamente più corto.

Per ottimizzare ulteriormente la navigazione, puoi implementare modificatori per la navigazione per calcolare percorsi più intelligenti attraverso vari materiali, attorno a regioni definite, o per ignorare ostacoli.
Costi dei materiali
Quando lavori con i materiali di Terrain e BasePart, puoi includere una tabella di Costs all'interno di CreatePath() per rendere certi materiali più facili da attraversare rispetto ad altri. Tutti i materiali hanno un costo predefinito di 1 e qualsiasi materiale può essere definito come non attraversabile impostando il suo valore a math.huge.
Le chiavi nella tabella Costs dovrebbero essere nomi di stringa che rappresentano i nomi di Enum.Material, ad esempio Water per Enum.Material.Water o CrackedLava per 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.01local VELOCITY_MULTIPLIER = 0.0625local path = PathfindingService:CreatePath({AgentCanClimb = true,Costs = {Water = 20, CrackedLava = 100, Slate = 20}})
Configura regioni
In alcuni casi, la preferenza per i materiali non è sufficiente. Ad esempio, potresti voler evitare a un personaggio di attraversare una regione definita, indipendentemente dai materiali sottostanti. Questo può essere realizzato aggiungendo un oggetto PathfindingModifier a una parte.
Crea una parte Anchored attorno alla regione e imposta la sua proprietà CanCollide su false.

Inserisci un'istanza di PathfindingModifier sulla parte, trova la sua proprietà Label e assegna un nome significativo come DangerZone.

Includi una chiave DangerZone corrispondente e un valore numerico associato all'interno della tabella Costs di CreatePath(). Un modificatore può essere definito come non attraversabile impostando il suo valore su 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.01local VELOCITY_MULTIPLIER = 0.0625local path = PathfindingService:CreatePath({AgentCanClimb = true,Costs = {DangerZone = math.huge, Water = 20, CrackedLava = 20, Slate = 20}})
Ignora ostacoli
In alcuni casi, è utile navigare attraverso ostacoli solidi come se non esistessero. Questo ti permette di calcolare un percorso attraverso specifici blocchi fisici, invece di far fallire direttamente il calcolo.
Crea una parte Anchored attorno all'oggetto e imposta la sua proprietà CanCollide su false.

Inserisci un'istanza di PathfindingModifier sulla parte e abilita la sua proprietà PassThrough.

Ora, quando viene calcolato un percorso dall'NPC zombie al personaggio del giocatore, il percorso si estende oltre la porta e puoi invitare lo zombie ad attraversarla. Anche se lo zombie non è in grado di aprire la porta, reagisce come se "sentisse" il personaggio dietro la porta.

Link di navigazione
A volte è necessario trovare un percorso attraverso uno spazio che non può essere normalmente attraversato, come un abisso, ed eseguire un'azione personalizzata per raggiungere il prossimo waypoint. Questo può essere ottenuto attraverso l'oggetto PathfindingLink.
Utilizzando l'esempio sopra, puoi far utilizzare una barca all'agente.

Per creare un PathfindingLink utilizzando questo esempio:
- OPZIONALEAttiva Link di Navigazione dalle Opzioni di Visualizzazione nel riquadro in alto a destra della vista 3D. Questo aiuta con la visualizzazione e il debug durante l'implementazione dei link di navigazione.
Crea due Attachments, uno sul sedile della barca e uno vicino al punto di attracco della barca.

Crea un oggetto PathfindingLink nello spazio di lavoro, quindi assegna le proprietà Attachment0 e Attachment1 rispettivamente agli attacchi di partenza e di arrivo.


Assegna un nome significativo come UseBoat alla sua proprietà Label. Questo nome viene utilizzato come flag nello script di navigazione per attivare un'azione personalizzata quando l'agente raggiunge il punto di partenza del link.

Includi una tabella di Costs all'interno di CreatePath() contenente sia una chiave Water che una chiave personalizzata che corrisponde al nome della proprietà Label. Assegna alla chiave personalizzata un valore inferiore a quello di 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.01local VELOCITY_MULTIPLIER = 0.0625local path = PathfindingService:CreatePath({AgentCanClimb = true,Costs = {UseBoat = 2, Water = 20}})Nella funzione moveToNextWaypoint() (
LINES 93–114), un controllo personalizzato per il nome del modificatore Label può essere utilizzato per compiere un'azione diversa da Humanoid:Move(); in questo caso, potresti chiamare una funzione per far sedere l'agente nella barca, muovere la barca attraverso l'acqua, far uscire l'agente al punto di attracco della barca e poi continuare il percorso dell'agente verso la sua destinazione finale.
Compatibilità con lo streaming
Lo streaming delle istanze in gioco è una potente funzionalità che carica e scarica dinamicamente contenuti 3D mentre il personaggio di un giocatore si muove nel mondo. Mentre esplorano lo spazio 3D, nuovi sottoinsiemi dello spazio vengono trasmessi al loro dispositivo e alcuni degli insiemi esistenti potrebbero uscire.
Considera i seguenti migliori pratiche per utilizzare PathfindingService in giochi abilitati per lo streaming:
Lo streaming può bloccare o sbloccare un percorso dato mentre un personaggio si muove lungo esso. Ad esempio, mentre un personaggio corre attraverso una foresta, un albero potrebbe apparire davanti a loro e ostruire il percorso. Per far funzionare la navigazione senza problemi con lo streaming, è altamente consigliato utilizzare la tecnica di gestione dei percorsi bloccati e ricalcolare il percorso quando necessario.
Un approccio comune nella navigazione è utilizzare le coordinate degli oggetti esistenti per il calcolo, come impostare una destinazione per il percorso sulla posizione di un modello TreasureChest esistente nel mondo. Questo approccio è completamente compatibile con gli Scripts lato server poiché il server ha sempre una visione completa del mondo, ma LocalScripts e ModuleScripts che vengono eseguiti sul client possono fallire se tentano di calcolare un percorso verso un oggetto che non è stato caricato.
Per affrontare questo problema, considera di impostare la destinazione sulla posizione di una BasePart all'interno di un modello persistente. I modelli persistenti vengono caricati subito dopo che il giocatore si unisce e non vengono mai scaricati, quindi uno script client-side può connettersi all'evento PersistentLoaded e accedere in sicurezza al modello per creare waypoint dopo che l'evento è scattato.
Limitazioni e fattori di fallimento
Il motore di navigazione include specifiche limitazioni per garantire un'elaborazione efficiente e prestazioni ottimali. Inoltre, i calcoli della navigazione possono fallire per vari motivi come indicato di seguito.