Navigazione è il processo di spostamento di un personaggio o oggetto (agente) lungo un percorso logico attorno agli ostacoli per raggiungere una destinazione, evitando opzionalmente materiali pericolosi o regioni definite.
Visualizzazione della navigazione
Per assistere con il layout e il debug della navigazione, Studio può renderizzare una mesh di navigazione e etichette di modificatori. Per abilitarli, attiva Mesh di navigazione e Modificatori di navigazione dal widget 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 attraverso 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 inseriscilo in StarterCharacterScripts.
PlayerPathFollow (LocalScript in StarterCharacterScripts)local PathfindingService = game:GetService("PathfindingService")local Players = game:GetService("Players")local RunService = game:GetService("RunService")local DESTINAZIONE = Vector3.new(20, 0.5, 20)local ATTESA_GROUND = 0.01local MOLTIPLICATORE_VELOCITÀ = 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 dall'umanoide 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, MOLTIPLICATORE_VELOCITÀ * velocity)-- Considera il waypoint raggiunto se meno della soglia davanti 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(ATTESA_GROUND)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 usare la barca all'agenteelseif nextWaypointAction == Enum.PathWaypointAction.Jump thenhumanoid:ChangeState(Enum.HumanoidStateType.Jumping)while humanoid.FloorMaterial ~= Enum.Material.Air dotask.wait(ATTESA_GROUND)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, DESTINAZIONE)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-- Ferma il rilevamento del blocco del percorso fino a quando il percorso non viene ricalcolatoblockedConnection:Disconnect()resetWaypointData()-- Chiama la funzione per ricalcolare un nuovo percorsofollowPath()endend)findStartingPoint(waypoints)moveToNextWaypoint()endfollowPath()Modifica la variabile DESTINAZIONE (
LINE 5) in una destinazione di tipo Vector3 all'interno del mondo 3D che il personaggio del giocatore può raggiungere.Procedi attraverso le seguenti sezioni per apprendere il calcolo del percorso e il movimento del personaggio.
Creazione del percorso
La navigazione viene avviata attraverso PathfindingService e il suo metodo CreatePath() (
| Chiave | Descrizione | Tipo | Predefinito |
|---|---|---|---|
| AgentRadius | Raggio dell'agente, in stud. Utile per determinare la separazione minima dagli ostacoli. | intero | 2 |
| AgentHeight | Altezza dell'agente, in stud. Spazio vuoto più piccolo di questo valore, come lo spazio sotto le scale, sarà contrassegnato come non attraversabile. | intero | 5 |
| AgentCanJump | Determina se il salto durante la navigazione è consentito. | booleano | true |
| AgentCanClimb | Determina se arrampicarsi su TrussParts durante la navigazione è consentito. Un percorso arrampicabile ha un'etichetta Label chiamata Climb e il costo per un percorso arrampicabile è 1 per impostazione predefinita. | booleano | false |
| WaypointSpacing | Distanza 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 traversata. Utile per far preferire all'agente determinati materiali/regioni rispetto ad altri. Vedi 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 waypoints che tracciano il percorso dall'inizio alla fine. Questi punti possono essere raccolti con il metodo Path:GetWaypoints() (

Movimento lungo il percorso
Ogni PathWaypoint consiste sia in una Position (Vector3) che in un Action (PathWaypointAction). Per muovere un personaggio contenente un Humanoid, come un tipico personaggio di Roblox, il modo migliore è chiamare Humanoid:Move() da waypoint a waypoint e utilizzare il callback isCurrentWaypointReached() dello script (
Percorsi bloccati
Molti mondi di Roblox sono dinamici; le parti potrebbero muoversi o cadere e i pavimenti potrebbero 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 ha bloccato (
Modificatori di navigazione
Per impostazione predefinita, Path:ComputeAsync() restituisce il percorso più breve tra il punto di partenza e la destinazione, con l'eccezione che cerca di evitare i salti. Questo appare innaturale in alcune situazioni; ad esempio, un percorso potrebbe attraversare 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 di 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 Costs all'interno di CreatePath() per rendere alcuni materiali più attraversabili di altri. Tutti i materiali hanno un costo predefinito di 1 e qualsiasi materiale può essere definito come non attraversabile impostando il suo valore su math.huge.
Le chiavi nella tabella Costs dovrebbero essere nomi stringa che rappresentano i nomi di Enum.Material, ad esempio Water per Enum.Material.Water o CrackedLava per Enum.Material.CrackedLava.
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local DESTINAZIONE = Vector3.new(20, 0.5, 20)
local ATTESA_GROUND = 0.01
local MOLTIPLICATORE_VELOCITÀ = 0.0625
local 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 entrare in una regione definita, indipendentemente dai materiali sottostanti. Questo può essere ottenuto 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 DESTINAZIONE = Vector3.new(20, 0.5, 20)local ATTESA_GROUND = 0.01local MOLTIPLICATORE_VELOCITÀ = 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 consente di calcolare un percorso attraverso specifici blocchi fisici, invece di far fallire 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 dal NPC zombie al personaggio del giocatore, il percorso si estende oltre la porta e puoi invitare lo zombie a 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 attraverso un baratro, e eseguire un'azione personalizzata per raggiungere il prossimo waypoint. Questo può essere ottenuto attraverso l'oggetto PathfindingLink.
Utilizzando l'esempio di cui sopra, puoi far usare una barca all'agente.

Per creare un PathfindingLink utilizzando questo esempio:
- OPZIONALEAttiva Link di navigazione dal widget Opzioni di visualizzazione nell'angolo in alto a destra della vista 3D. Questo aiuta con la visualizzazione e il debug quando si implementano i link di navigazione.
Crea due Attachments, uno sul sedile della barca e uno vicino al punto di atterraggio della barca.

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


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 inizio del link.

Includi una tabella 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 DESTINAZIONE = Vector3.new(20, 0.5, 20)local ATTESA_GROUND = 0.01local MOLTIPLICATORE_VELOCITÀ = 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 eseguire un'azione diversa da Humanoid:Move(); in questo caso, potresti chiamare una funzione per sedere l'agente nella barca, muovere la barca attraverso l'acqua, dissedere l'agente al punto di atterraggio 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 dei sottoinsiemi esistenti potrebbero essere scaricati.
Considera le seguenti migliori pratiche per utilizzare PathfindingService nei giochi abilitati per lo streaming:
Lo streaming può bloccare o sbloccare un dato percorso mentre un personaggio si muove lungo di esso. Ad esempio, mentre un personaggio corre attraverso una foresta, un albero potrebbe essere trasmesso in un punto davanti a loro e ostruire il percorso. Per far funzionare la navigazione senza problemi con lo streaming, è altamente consigliato utilizzare la tecnica 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 del percorso sulla posizione di un modello TreasureChest esistente nel mondo. Questo approccio è completamente compatibile con gli Scripts lato server poiché il server ha una visione completa del mondo in ogni momento, ma gli LocalScripts e ModuleScripts che vengono eseguiti sul client potrebbero fallire se tentano di calcolare un percorso verso un oggetto che non è stato trasmesso.
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 l'ingresso del giocatore e non vengono mai scaricati, quindi uno script lato client può connettersi all'evento PersistentLoaded e accedere in sicurezza al modello per creare waypoint dopo che l'evento è stato attivato.
Limitazioni e fattori di fallimento
Il motore di navigazione include limitazioni specifiche per garantire un'elaborazione efficiente e prestazioni ottimali. Inoltre, i calcoli di navigazione possono fallire per vari motivi come delineato di seguito.