Padvinden

*Deze inhoud is vertaald met behulp van AI (bèta) en kan fouten bevatten. Klik hier om deze pagina in het Engels te bekijken.

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.

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.

Een close-up van het 3D-venster met de Visualisatie-opties knop aangegeven in de rechterbovenhoek.

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.

Navigatiemesh weergegeven in Studio

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:

  1. BELANGRIJK
    In de Explorer, selecteer de StarterPlayer container. Stel vervolgens in het Eigenschappen venster zowel DevComputerMovementMode als DevTouchMovementMode in op Scriptable.

  2. 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.01
    local VELOCITY_MULTIPLIER = 0.0625
    local path = PathfindingService:CreatePath({
    AgentCanClimb = true,
    Costs = {
    Water = 20
    }
    })
    local character = script.Parent
    local humanoid = character:WaitForChild("Humanoid")
    local waypoints
    local nextWaypointIndex
    local blockedConnection
    local currentWaypointReachedConnection
    local currentWaypointPlaneNormal = Vector3.zero
    local currentWaypointPlaneDistance = 0
    local pathfinderWorking = false
    local function disconnectCurrentWaypointReachedConnection()
    if not currentWaypointReachedConnection then return end
    currentWaypointReachedConnection:Disconnect()
    currentWaypointReachedConnection = nil
    end
    local function isCurrentWaypointReached()
    if humanoid.FloorMaterial == Enum.Material.Air then
    return false
    end
    local reached = false
    if currentWaypointPlaneNormal ~= Vector3.zero then
    -- Bereken de afstand van humanoid tot het bestemmingsvlak
    local dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance
    -- Bereken de component van de humanoïde snelheid die naar het vlak is gericht
    local velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)
    -- Bereken de drempel van het bestemmingsvlak op basis van de snelheid van de humanoïde
    local threshold = math.max(1.0, VELOCITY_MULTIPLIER * velocity)
    -- Beschouw waypoint als bereikt als minder dan drempel voor het vlak
    reached = dist < threshold
    else
    reached = true
    end
    if reached then
    currentWaypointPlaneNormal = Vector3.zero
    currentWaypointPlaneDistance = 0
    moveToNextWaypoint()
    end
    end
    local function calculateNextWaypointApproach()
    nextWaypointIndex += 1
    if nextWaypointIndex > #waypoints then
    return false
    end
    local currentWaypoint = waypoints[nextWaypointIndex - 1]
    local nextWaypoint = waypoints[nextWaypointIndex]
    -- Bouw bestemmingsvlak van volgende waypoint naar huidige
    currentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position
    -- Zet normaal loodrecht op het Y-vlak wanneer niet omhoog klimmend
    if nextWaypoint.Label ~= "Climb" then
    currentWaypointPlaneNormal = Vector3.new(currentWaypointPlaneNormal.X, 0, currentWaypointPlaneNormal.Z)
    end
    if currentWaypointPlaneNormal.Magnitude > 0.000001 then
    currentWaypointPlaneNormal = currentWaypointPlaneNormal.Unit
    currentWaypointPlaneDistance = currentWaypointPlaneNormal:Dot(nextWaypoint.Position)
    end
    return true
    end
    local function resetWaypointData()
    humanoid:Move(Vector3.zero)
    currentWaypointPlaneNormal = Vector3.zero
    currentWaypointPlaneDistance = 0
    disconnectCurrentWaypointReachedConnection()
    pathfinderWorking = false
    end
    local function waitForGround()
    while humanoid.FloorMaterial == Enum.Material.Air do
    task.wait(GROUND_WAIT)
    end
    end
    function moveToNextWaypoint()
    if calculateNextWaypointApproach() then
    disconnectCurrentWaypointReachedConnection()
    currentWaypointReachedConnection = RunService.Heartbeat:Connect(isCurrentWaypointReached)
    local nextWaypointPosition = waypoints[nextWaypointIndex].Position
    local nextWaypointAction = waypoints[nextWaypointIndex].Action
    humanoid:Move(nextWaypointPosition - humanoid.RootPart.Position)
    if waypoints[nextWaypointIndex + 1] and waypoints[nextWaypointIndex + 1].Label == "UseBoat" then
    nextWaypointIndex += 1
    -- Roep je eigen aangepaste functie aan om de agent de boot te laten gebruiken
    elseif nextWaypointAction == Enum.PathWaypointAction.Jump then
    humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
    while humanoid.FloorMaterial ~= Enum.Material.Air do
    task.wait(GROUND_WAIT)
    end
    humanoid:Move(nextWaypointPosition - humanoid.RootPart.Position)
    end
    else
    resetWaypointData()
    end
    end
    local function findStartingPoint(waypoints)
    nextWaypointIndex = 1
    while nextWaypointIndex + 1 <= #waypoints do
    local dist = waypoints[nextWaypointIndex + 1].Position - humanoid.RootPart.Position
    dist = Vector3.new(dist.X, 0, dist.Z)
    if dist.magnitude >= 2 then
    return
    end
    nextWaypointIndex += 1
    end
    end
    local function followPath()
    -- Bereken het pad
    pathfinderWorking = true
    waitForGround()
    local success, errorMessage = pcall(function()
    path:ComputeAsync(character.PrimaryPart.Position, DESTINATION)
    end)
    if not success or path.Status ~= Enum.PathStatus.Success then
    warn("Pad niet berekend!", errorMessage)
    return
    end
    -- Verkrijg de padwaypoints
    waypoints = path:GetWaypoints()
    -- Detecteer of het pad geblokkeerd raakt
    blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
    -- Controleer of het obstakel verderop op het pad ligt
    if blockedWaypointIndex >= nextWaypointIndex then
    -- Stop met het detecteren van padblokkades totdat het pad opnieuw is berekend
    blockedConnection:Disconnect()
    resetWaypointData()
    -- Roep functie aan om nieuw pad opnieuw te berekenen
    followPath()
    end
    end)
    findStartingPoint(waypoints)
    moveToNextWaypoint()
    end
    followPath()
  3. Bewerk de DESTINATION variabele (

    LIJN 5
    ) naar een Vector3 bestemming binnen de 3D-wereld die het spelerkarakter kan bereiken.

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

LIJNEN 9–14
). Deze methode accepteert een optionele tabel van parameters die verfijnen hoe het karakter (agent) zich langs het pad beweegt.

SleutelBeschrijvingTypeStandaard
AgentRadiusAgentradius, in studs. Nuttig voor het bepalen van de minimale scheiding van obstakels.integer2
AgentHeightAgenthoogte, in studs. Lege ruimte kleiner dan deze waarde, zoals de ruimte onder trappen, wordt gemarkeerd als niet-overbrugbaar.integer5
AgentCanJumpBepaalt of springen tijdens padvinden is toegestaan.booleantrue
AgentCanClimbBepaalt 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.booleanfalse
WaypointSpacingAfstand tussen tussenliggende waypoints in het pad. Als ingesteld op math.huge, zijn er geen tussenliggende waypoints.number4
CostsTabel 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.tablenil

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 (

LIJNEN 133–139
).

Pad start/eind gemarkeerd over twee bruggen

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 (

LIJN 142
). De geretourneerde array is gerangschikt in volgorde van waypoints van padstart tot pad eind.

Waypoints aangegeven over berekend pad
Waypoints aangegeven over berekend pad

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 (

LIJNEN 32–56
) om te detecteren wanneer het karakter elk waypoint bereikt.

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 (

LIJNEN 145–154
).

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.

Twee paden aangegeven met het kortere pad niet noodzakelijkerwijs logischer

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.

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

  1. Maak een Anchored onderdeel rond de regio en stel de CanCollide eigenschap in op false.

    Geankerd onderdeel dat een regio definieert om een padvindmodificator op toe te passen.
  2. Voeg een PathfindingModifier instantie toe aan het onderdeel, zoek de Label eigenschap en wijs een betekenisvolle naam toe zoals DangerZone.

    PathfindingModifier instantie met Label eigenschap ingesteld op DangerZone.
  3. 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.01
    local VELOCITY_MULTIPLIER = 0.0625
    local 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.

  1. Maak een Anchored onderdeel rond het object en stel de CanCollide eigenschap in op false.

    Geankerd onderdeel dat een regio definieert om een padvindmodificator op toe te passen.
  2. Voeg een PathfindingModifier instantie toe aan het onderdeel en schakel de PassThrough eigenschap in.

    PathfindingModifier instantie met PassThrough eigenschap ingeschakeld.

    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.

    Zombie NPC pad dat door de eerder blokkeren deur gaat.

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.

PathfindingLink die laat zien hoe een agent een boot kan gebruiken.

Om een PathfindingLink te maken met dit voorbeeld:

  1. OPTIONEEL
    Zet 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.

  2. Maak twee Attachments, één op de zitplaats van de boot en één nabij het landingspunt van de boot.

    Attachments gemaakt voor het begin en einde van de padvindlink.
  3. Maak een PathfindingLink object in de workspace, en wijs vervolgens de Attachment0 en Attachment1 eigenschappen toe aan de start- en eindattachments respectievelijk.

    Attachment0/Attachment1 eigenschappen van een PathfindingLink. PathfindingLink gevisualiseerd in de 3D-wereld.
  4. 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.

    Label eigenschap gespecificeerd voor PathfindingLink.
  5. 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.01
    local VELOCITY_MULTIPLIER = 0.0625
    local path = PathfindingService:CreatePath({
    AgentCanClimb = true,
    Costs = {
    UseBoat = 2, Water = 20
    }
    })
  6. 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.

©2026 Roblox Corporation. Roblox, het Roblox-logo en Powering Imagination behoren tot onze geregistreerde en niet-geregistreerde handelsmerken in de VS en andere landen.