Padvinden is het proces van het verplaatsen van een karakter of object (agent) langs een logisch pad rond obstakels om een bestemming te bereiken, waarbij optioneel gevaarlijke materialen of gedefinieerde gebieden worden vermeden.
Navigatievisualisatie
Om te helpen bij de lay-out en het debuggen van padvinden, kan Studio een navigatiemesh en modificator labels weergeven. Om ze in te schakelen, zet je Navigatiemesh en Padvindmodificatoren aan vanuit de Visualisatie-opties widget in de rechterbovenhoek van het 3D-venster.

Met Navigatiemesh ingeschakeld, tonen gekleurde gebieden waar een karakter kan lopen of zwemmen. Kleine pijlen geven gebieden aan die een karakter zal proberen te bereiken door te springen.

Implementatie
Hoewel padvinden op verschillende manieren kan worden geïmplementeerd via PathfindingService en de bijbehorende methoden zoals CreatePath(), gebruikt deze sectie het volgende padvindscript voor het karakter van de speler.
Om te testen terwijl je leest:
- BELANGRIJKIn de Explorer, selecteer de StarterPlayer container. Stel vervolgens in het Eigenschappen venster zowel DevComputerMovementMode als DevTouchMovementMode in op Scriptable.


Kopieer de volgende code in een LocalScript binnen StarterCharacterScripts, of haal dit pakket op en plaats het 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-- Bereken de afstand van humanoid tot het bestemmingsvlaklocal dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance-- Bereken de component van de humanoïde snelheid die naar het vlak is gerichtlocal velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)-- Bereken de drempel van het bestemmingsvlak op basis van de snelheid van de humanoïdelocal threshold = math.max(1.0, VELOCITY_MULTIPLIER * velocity)-- Beschouw waypoint als bereikt als minder dan drempel voor het vlakreached = 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]-- Bouw bestemmingsvlak van volgende waypoint naar huidigecurrentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position-- Zet normaal loodrecht op het Y-vlak wanneer niet omhoog klimmendif 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-- Roep je eigen aangepaste functie aan om de agent de boot te laten gebruikenelseif 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()-- Bereken het padpathfinderWorking = truewaitForGround()local success, errorMessage = pcall(function()path:ComputeAsync(character.PrimaryPart.Position, DESTINATION)end)if not success or path.Status ~= Enum.PathStatus.Success thenwarn("Pad niet berekend!", errorMessage)returnend-- Verkrijg de padwaypointswaypoints = path:GetWaypoints()-- Detecteer of het pad geblokkeerd raaktblockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)-- Controleer of het obstakel verderop op het pad ligtif blockedWaypointIndex >= nextWaypointIndex then-- Stop met het detecteren van padblokkades totdat het pad opnieuw is berekendblockedConnection:Disconnect()resetWaypointData()-- Roep functie aan om nieuw pad opnieuw te berekenenfollowPath()endend)findStartingPoint(waypoints)moveToNextWaypoint()endfollowPath()Bewerk de DESTINATION variabele (
LIJN 5) naar een Vector3 bestemming binnen de 3D-wereld die het spelerkarakter kan bereiken.Ga verder door de volgende secties om meer te leren over padberekening en karakterbeweging.
Padcreatie
Padvinden wordt geïnitieerd via PathfindingService en zijn CreatePath() methode (
| Sleutel | Beschrijving | Type | Standaard |
|---|---|---|---|
| AgentRadius | Agentradius, in studs. Nuttig voor het bepalen van de minimale scheiding van obstakels. | integer | 2 |
| AgentHeight | Agenthoogte, in studs. Lege ruimte kleiner dan deze waarde, zoals de ruimte onder trappen, wordt gemarkeerd als niet-overbrugbaar. | integer | 5 |
| AgentCanJump | Bepaalt of springen tijdens padvinden is toegestaan. | boolean | true |
| AgentCanClimb | Bepaalt of klimmen TrussParts tijdens padvinden is toegestaan. Een klimbaar pad heeft een Label genaamd Climb en de kosten voor een klimbaar pad is standaard 1. | boolean | false |
| WaypointSpacing | Afstand tussen tussenliggende waypoints in het pad. Als ingesteld op math.huge, zijn er geen tussenliggende waypoints. | number | 4 |
| Costs | Tabel van materialen of gedefinieerde PathfindingModifiers en hun kosten voor traversie. Nuttig om de agent bepaalde materialen/gebieden boven andere te laten verkiezen. Zie modificatoren voor details. | table | nil |
Padberekening
Nadat je een geldig pad hebt gemaakt met CreatePath(), moet het berekend worden door Path:ComputeAsync() aan te roepen met een Vector3 voor zowel het startpunt als de bestemming (

Zodra de Path is berekend, bevat deze een reeks waypoints die het pad van begin tot eind volgen. Deze punten kunnen worden verzameld met de Path:GetWaypoints() methode (

Padbeweging
Elke PathWaypoint bestaat uit zowel een Position (Vector3) als Action (PathWaypointAction). Om een karakter met een Humanoid, zoals een typisch Roblox-karakter, te verplaatsen, is de beste manier om Humanoid:Move() van waypoint naar waypoint aan te roepen en de callback isCurrentWaypointReached() van het script te gebruiken (
Geblokkeerde paden
Veel Roblox-werelden zijn dynamisch; onderdelen kunnen bewegen of vallen en vloeren kunnen instorten. Dit kan een berekend pad blokkeren en voorkomen dat het karakter zijn bestemming bereikt. Om dit te verhelpen, kun je de Path.Blocked gebeurtenis verbinden en het pad opnieuw berekenen rond wat het blokkeert (
Padvindmodificatoren
Standaard retourneert Path:ComputeAsync() het kortste pad tussen het startpunt en de bestemming, met de uitzondering dat het probeert sprongen te vermijden. Dit ziet er in sommige situaties onnatuurlijk uit; bijvoorbeeld, een pad kan door moeraswater gaan in plaats van eromheen simpelweg omdat het pad door het water geometrisch korter is.

Om padvinden nog verder te optimaliseren, kun je padvindmodificatoren implementeren om slimmer te berekenen over verschillende materialen, rond gedefinieerde gebieden, of om obstakels te negeren.
Materiaalkosten
Bij het werken met Terrain en BasePart materialen, kun je een Costs tabel opnemen binnen CreatePath() om bepaalde materialen beter begaanbaar te maken dan andere. Alle materialen hebben een standaardkost van 1 en elk materiaal kan als niet-overbrugbaar worden gedefinieerd door de waarde in te stellen op math.huge.
Sleutels in de Costs tabel moeten string namen zijn die de Enum.Material namen vertegenwoordigen, bijvoorbeeld Water voor Enum.Material.Water of CrackedLava voor Enum.Material.CrackedLava.
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.01
local VELOCITY_MULTIPLIER = 0.0625
local path = PathfindingService:CreatePath({
AgentCanClimb = true,
Costs = {
Water = 20, CrackedLava = 100, Slate = 20
}
})Regio's configureren
In sommige gevallen is materiaalvoorkeur niet genoeg. Bijvoorbeeld, je wilt misschien dat karakters een gedefinieerde regio vermijden, ongeacht de materialen onder hun voeten. Dit kan worden bereikt door een PathfindingModifier object aan een onderdeel toe te voegen.
Maak een Anchored onderdeel rond de regio en stel de CanCollide eigenschap in op false.

Voeg een PathfindingModifier instantie toe aan het onderdeel, zoek de Label eigenschap en wijs een betekenisvolle naam toe zoals DangerZone.

Voeg een bijpassende DangerZone sleutel en bijbehorende numerieke waarde toe binnen de Costs tabel van CreatePath(). Een modificator kan als niet-overbrugbaar worden gedefinieerd door de waarde in te stellen op 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}})
Obstakels negeren
In sommige gevallen is het nuttig om door solide obstakels te padvinden alsof ze niet bestaan. Dit stelt je in staat om een pad te berekenen door specifieke fysieke blokkades, in plaats van dat de berekening volledig mislukt.
Maak een Anchored onderdeel rond het object en stel de CanCollide eigenschap in op false.

Voeg een PathfindingModifier instantie toe aan het onderdeel en schakel de PassThrough eigenschap in.

Nu, wanneer een pad wordt berekend van de zombie NPC naar het spelerkarakter, strekt het pad zich uit voorbij de deur en kun je de zombie aanmoedigen om deze te doorkruisen. Zelfs als de zombie de deur niet kan openen, reageert hij alsof hij "hoort" dat het karakter achter de deur is.

Padvindlinks
Soms is het nodig om een pad te vinden over een ruimte die normaal niet kan worden overbrugd, zoals over een afgrond, en een aangepaste actie uit te voeren om het volgende waypoint te bereiken. Dit kan worden bereikt via het PathfindingLink object.
Met het voorbeeld hierboven kun je de agent een boot laten gebruiken.

Om een PathfindingLink te maken met dit voorbeeld:
- OPTIONEELZet Padvindlinks aan vanuit de Visualisatie-opties widget in de rechterbovenhoek van het 3D-venster. Dit helpt bij visualisatie en debugging bij het implementeren van padvindlinks.
Maak twee Attachments, één op de zitplaats van de boot en één nabij het landingspunt van de boot.

Maak een PathfindingLink object in de workspace, en wijs vervolgens de Attachment0 en Attachment1 eigenschappen toe aan de start- en eindattachments respectievelijk.


Wijs een betekenisvolle naam zoals UseBoat toe aan de Label eigenschap. Deze naam wordt gebruikt als een vlag in het padvindscript om een aangepaste actie te activeren wanneer de agent het startlinkpunt bereikt.

Voeg een Costs tabel toe binnen CreatePath() met zowel een Water sleutel als een aangepaste sleutel die overeenkomt met de Label eigenschap naam. Wijs de aangepaste sleutel een waarde lager toe dan die van 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}})In de moveToNextWaypoint() functie (
LIJNEN 93–114), kan een aangepaste controle voor de Label modificatornaam worden gebruikt om een andere actie dan Humanoid:Move() uit te voeren; in dit geval kun je een functie aanroepen om de agent in de boot te laten zitten, de boot over het water te verplaatsen, de agent op het landingspunt van de boot te laten uitstappen, en vervolgens het pad van de agent naar zijn uiteindelijke bestemming voort te zetten.
Streamingcompatibiliteit
In-game instantie streaming is een krachtige functie die 3D-inhoud dynamisch laadt en ontlaadt terwijl het karakter van een speler zich door de wereld beweegt. Terwijl ze de 3D-ruimte verkennen, worden nieuwe subsets van de ruimte naar hun apparaat gestreamd en sommige van de bestaande subsets kunnen eruit gestreamd worden.
Overweeg de volgende best practices voor het gebruik van PathfindingService in streaming-geactiveerde games:
Streaming kan een bepaald pad blokkeren of deblokkeren terwijl een karakter erlangs beweegt. Bijvoorbeeld, terwijl een karakter door een bos rent, kan er ergens voor hen een boom in streamen en het pad blokkeren. Om padvinden naadloos te laten werken met streaming, is het sterk aanbevolen om de geblokkeerde paden techniek te gebruiken en het pad opnieuw te berekenen wanneer dat nodig is.
Een veelgebruikte aanpak in padvinden is om de coördinaten van bestaande objecten te gebruiken voor berekening, zoals het instellen van een padbestemming op de positie van een bestaand TreasureChest model in de wereld. Deze aanpak is volledig compatibel met server-side Scripts aangezien de server te allen tijde een volledig overzicht van de wereld heeft, maar LocalScripts en ModuleScripts die op de client draaien, kunnen mislukken als ze proberen een pad te berekenen naar een object dat niet is gestreamd.
Om dit probleem aan te pakken, overweeg dan om de bestemming in te stellen op de positie van een BasePart binnen een permanent model. Permanente modellen worden kort na het joinen van de speler geladen en ze worden nooit uitgestreamd, zodat een client-side script kan verbinden met de PersistentLoaded gebeurtenis en veilig toegang kan krijgen tot het model voor het creëren van waypoints nadat de gebeurtenis is afgevuurd.
Beperkingen en faalfactoren
De padvindengine heeft specifieke beperkingen om efficiënte verwerking en optimale prestaties te waarborgen. Bovendien kunnen padvind berekeningen om verschillende redenen mislukken, zoals hieronder beschreven.