Poszukiwanie ścieżek

*Ta zawartość została przetłumaczona przy użyciu narzędzi AI (w wersji beta) i może zawierać błędy. Aby wyświetlić tę stronę w języku angielskim, kliknij tutaj.

Poszukiwanie ścieżek to proces poruszania się postacią lub obiektem (agentem) wzdłuż logicznej trasy wokół przeszkód w celu dotarcia do celu, opcjonalnie unikając materiałów niebezpiecznych lub zdefiniowanych obszarów.

Wizualizacja nawigacji

Aby wspomóc układ i debugowanie poszukiwania ścieżek, Studio może renderować siatkę nawigacyjną oraz etykiety modyfikatorów. Aby je włączyć, przesuń suwak na Siatka nawigacyjna i Modyfikatory poszukiwania ścieżek z widgetu Opcje wizualizacji w prawym górnym rogu widoku 3D.

Zbliżenie 3D widoku z zaznaczoną ikoną Opcji wizualizacji w prawym górnym rogu.

Z włączoną Siatką nawigacyjną kolorowe obszary pokazują, gdzie postać może iść lub pływać. Małe strzałki wskazują obszary, które postać spróbuje osiągnąć skacząc.

Siatka nawigacyjna w Studiu

Implementacja

Chociaż poszukiwanie ścieżek można zaimplementować na różne sposoby za pomocą PathfindingService i jego powiązanych metod, takich jak CreatePath(), ta sekcja używa następującego skryptu poszukiwania ścieżek dla postaci gracza.

Aby przetestować podczas czytania:

  1. WAŻNE
    W Eksploratorze wybierz kontener StarterPlayer. Następnie w oknie Właściwości ustaw zarówno DevComputerMovementMode, jak i DevTouchMovementMode na Scriptable.

  2. Skopiuj poniższy kod do LocalScript w StarterCharacterScripts lub pobierz ten pakiet i wrzuć go do StarterCharacterScripts.

    PlayerPathFollow (LocalScript w 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
    -- Oblicz odległość od humanoida do płaszczyzny celu
    local dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance
    -- Oblicz komponent prędkości humanoida zmierzający w kierunku płaszczyzny
    local velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)
    -- Oblicz próg od płaszczyzny celu na podstawie prędkości humanoida
    local threshold = math.max(1.0, VELOCITY_MULTIPLIER * velocity)
    -- Rozważ waypoint jako osiągnięty, jeśli odległość jest mniejsza niż próg przed płaszczyzną
    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]
    -- Zbuduj płaszczyznę docelową z następnego waypointu w kierunku aktualnego
    currentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position
    -- Ustaw normalną na prostopadłą do płaszczyzny Y, gdy nie wspinamy się
    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
    -- Wywołaj własną dostosowaną funkcję, aby sprawić, że agent użyje łodzi
    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()
    -- Oblicz ścieżkę
    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("Ścieżka nie została obliczona!", errorMessage)
    return
    end
    -- Pobierz waypoints ścieżki
    waypoints = path:GetWaypoints()
    -- Wykryj, czy ścieżka stała się zablokowana
    blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
    -- Sprawdź, czy przeszkoda znajduje się dalej na ścieżce
    if blockedWaypointIndex >= nextWaypointIndex then
    -- Przestań wykrywać zablokowanie ścieżki, dopóki nie obliczy się nowa ścieżka
    blockedConnection:Disconnect()
    resetWaypointData()
    -- Wywołaj funkcję, aby obliczyć nową ścieżkę
    followPath()
    end
    end)
    findStartingPoint(waypoints)
    moveToNextWaypoint()
    end
    followPath()
  3. Edytuj zmienną DESTINATION (

    LINIA 5
    ) na cel typu Vector3, do którego postać gracza może dotrzeć w świecie 3D.

  4. Przejdź через следующие разделы, чтобы узнать о вычислении пути и движении персонажа.

Tworzenie ścieżki

Poszukiwanie ścieżek inicjuje się za pomocą PathfindingService oraz metody CreatePath() (

LINIE 9–14
). Ta metoda akceptuje opcjonalną tabelę parametrów, które dostosowują sposób, w jaki postać (agent) porusza się wzdłuż ścieżki.

KluczOpisTypDomyślnie
AgentRadiusPromień agenta, w studach. Przydatne do określenia minimalnego odstępu od przeszkód.integer2
AgentHeightWysokość agenta, w studach. Puste przestrzenie mniejsze niż ta wartość, jak przestrzeń pod schodami, będą oznaczone jako nieprzechodnie.integer5
AgentCanJumpOkreśla, czy skakanie podczas poszukiwania ścieżek jest dozwolone.booleantrue
AgentCanClimbOkreśla, czy wspinanie się na TrussParts podczas poszukiwania ścieżek jest dozwolone. Wspinaczka, której ścieżka ma Label nazwane Climb, ma domyślny koszt wynoszący 1.booleanfalse
WaypointSpacingOdległość między pośrednimi waypointami na ścieżce. Jeśli ustawione na math.huge, nie będzie pośrednich waypointów.number4
CostsTabela materiałów lub zdefiniowanych PathfindingModifiers i ich kosztów za przejście. Przydatne do preferowania przez agenta pewnych materiałów/regionów nad innymi. Zobacz modyfikatory dla szczegółów.tablenil

Obliczanie ścieżki

Po tym, jak utworzono ważną ścieżkę za pomocą CreatePath(), należy ją obliczyć poprzez wywołanie Path:ComputeAsync() z Vector3 dla obu punktów początkowego i docelowego (

LINIE 133–139
).

Początek/koniec ścieżki oznaczony na dwóch mostach

Gdy Path zostanie obliczona, zawierać będzie serię waypointów, które wyznaczają ścieżkę od początku do końca. Te punkty można zebrać za pomocą metody Path:GetWaypoints() (

LINIA 142
). Zwracana tablica jest uporządkowana według waypointów od początku ścieżki do jej końca.

Waypointy wskazane na obliczonej ścieżce
Waypointy wskazane na obliczonej ścieżce

Ruch na ścieżce

Każdy PathWaypoint składa się zarówno z Position (Vector3), jak i Action (PathWaypointAction). Aby poruszać się postacią zawierającą Humanoid, jak typowa postać Roblox, najlepszym sposobem jest wywołanie Humanoid:Move() między waypointami i użycie funkcji zwrotnej isCurrentWaypointReached() w skrypcie (

LINIE 32–56
), aby wykryć, kiedy postać osiąga każdy waypoint.

Zablokowane ścieżki

Wiele światów Roblox jest dynamicznych; części mogą poruszać się lub spadać, a podłogi mogą się załamać. Może to zablokować obliczoną ścieżkę i uniemożliwić postaci dotarcie do celu. Aby to obsłużyć, możesz połączyć się z zdarzeniem Path.Blocked i obliczyć ponownie ścieżkę wokół przeszkody (

LINIE 145–154
).

Modyfikatory poszukiwania ścieżek

Domyślnie Path:ComputeAsync() zwraca najkrótszą ścieżkę między punktem początkowym a celem, z wyjątkiem tego, że stara się unikać skoków. Wygląda to nienaturalnie w niektórych sytuacjach; na przykład, ścieżka może przechodzić przez wodę błotną, zamiast wokół niej, tylko dlatego, że ścieżka przez wodę jest geometrycznie krótsza.

Dwie ścieżki wskazane, przy czym krótsza ścieżka niekoniecznie bardziej logiczna

Aby zoptymalizować poszukiwanie ścieżek jeszcze bardziej, możesz wdrożyć modyfikatory poszukiwania ścieżek, aby obliczać mądrzejsze ścieżki przez różne materiały, wokół zdefiniowanych obszarów lub aby zignorować przeszkody.

Koszty materiałów

Pracując z materiałami Terrain i BasePart, możesz dołączyć tabelę Costs do CreatePath(), aby sprawić, że niektóre materiały będą łatwiejsze do przejścia niż inne. Wszystkie materiały mają domyślny koszt 1, a każdy materiał można zdefiniować jako nieprzechodni, ustawiając jego wartość na math.huge.

Klucze w tabeli Costs powinny być string nazwami reprezentującymi nazwy Enum.Material, na przykład Water dla Enum.Material.Water lub CrackedLava dla 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
}
})

Konfiguracja regionów

W niektórych przypadkach preferencje materiałowe nie są wystarczające. Na przykład, możesz chcieć, aby postacie unikały zdefiniowanego regionu, niezależnie od materiałów pod stopami. Można to osiągnąć, dodając obiekt PathfindingModifier do części.

  1. Stwórz część Anchored wokół regionu i ustaw jej właściwość CanCollide na false.

    Zablokowana część definiująca region, na który należy zastosować modyfikator poszukiwania ścieżek.
  2. Wstaw instancję PathfindingModifier na część, zlokalizuj jej właściwość Label i przypisz znaczną nazwę, taką jak DangerZone.

    Instancja PathfindingModifier z właściwością Label ustawioną na DangerZone.
  3. Dołącz odpowiadający klucz DangerZone i powiązaną wartość numeryczną w tabeli Costs CreatePath(). Modyfikator można zdefiniować jako nieprzechodni, ustawiając jego wartość na 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
    }
    })

Ignoruj przeszkody

W niektórych przypadkach przydatne jest poszukiwanie ścieżek przez solidne przeszkody, tak jakby nie istniały. To pozwala obliczyć ścieżkę przez konkretne fizyczne blokady, zamiast żeby obliczenia po prostu się nie powiodły.

  1. Stwórz część Anchored wokół obiektu i ustaw jej właściwość CanCollide na false.

    Zablokowana część definiująca region, na który należy zastosować modyfikator poszukiwania ścieżek.
  2. Wstaw instancję PathfindingModifier na część i włącz jej właściwość PassThrough.

    Instancja PathfindingModifier z włączoną właściwością PassThrough.

    Teraz, gdy ścieżka jest obliczana z NPC zombie do postaci gracza, ścieżka wydłuża się poza drzwiami, a ty możesz nakazać zombie przejść przez nie. Nawet jeśli zombie nie może otworzyć drzwi, reaguje tak, jakby "słyszało" postać za nimi.

    Ścieżka NPC zombie przechodząca przez wcześniej blokujące drzwi.

Linki poszukiwania ścieżek

Czasami konieczne jest znalezienie ścieżki w przestrzeni, która nie może być normalnie pokonywana, takiej jak przejście przez przepaść, i przeprowadzenie niestandardowej akcji w celu dotarcia do następnego waypointu. Można to osiągnąć przez obiekt PathfindingLink.

Używając przykładu z góry, możesz sprawić, że agent użyje łodzi.

PathfindingLink pokazujący, jak agent może użyć łodzi.

Aby stworzyć PathfindingLink przy użyciu tego przykładu:

  1. OPTIONAL
    Włącz Linki poszukiwania ścieżek z widgetu Opcje wizualizacji w prawym górnym rogu widoku 3D. Ułatwia to wizualizację i debugowanie podczas implementacji linków poszukiwania ścieżek.

  2. Stwórz dwa Attachments, jeden na siedzeniu łodzi, a drugi blisko punktu lądowania łodzi.

    Złączki utworzone dla początku i końca linku poszukiwania.
  3. Stwórz obiekt PathfindingLink w workspace, a następnie przypisz jego właściwości Attachment0 i Attachment1 do odpowiednich złączek startowych i końcowych.

    Właściwości Attachment0/Attachment1 linku poszukiwania. Link poszukiwania zobrazowany w świecie 3D.
  4. Przypisz znaczną nazwę, taką jak UseBoat, do właściwości Label. Ta nazwa jest używana jako znacznik w skrypcie poszukiwania ścieżek do wywołania niestandardowej akcji, gdy agent osiągnie punkt startowy linku.

    Właściwość Label określona dla Linku poszukiwania.
  5. Dołącz tabelę Costs w CreatePath(), zawierającą zarówno klucz Water, jak i niestandardowy klucz odpowiadający nazwie właściwości Label. Przypisz niestandardowemu kluczowi wartość mniejszą niż wartość 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. W funkcji moveToNextWaypoint() (

    LINIE 93–114
    ) można użyć niestandardowego sprawdzenia dla nazwy modyfikatora Label, aby podjąć inną akcję niż Humanoid:Move(); w tym przypadku możesz wywołać funkcję, aby posadzić agenta w łodzi, przesunąć łódź przez wodę, wysadzić agenta w punkcie lądowania łodzi i następnie kontynuować ścieżkę agenta do ostatecznego celu.

Zgodność ze strumieniowaniem

Strumieniowanie instancji w grze to potężna funkcja, która dynamicznie ładuje i odłącza zawartość 3D w miarę poruszania się postaci gracza po świecie. Podczas, gdy eksplorują przestrzeń 3D, nowe podzbiory przestrzeni strumieniują do ich urządzenia, a niektóre z istniejących podzbiorów mogą strumieniować na zewnątrz.

Rozważ poniższe najlepsze praktyki dotyczące korzystania z PathfindingService w grach z włączonym strumieniowaniem:

  • Strumieniowanie może blokować lub odblokowywać daną ścieżkę, gdy postać po niej porusza się. Na przykład, gdy postać biegnie przez las, drzewo może pojawić się z przodu i zablokować ścieżkę. Aby poszukiwanie ścieżek działało płynnie ze strumieniowaniem, zaleca się korzystanie z techniki obsługi zablokowanych ścieżek i ponowne obliczanie ścieżki, gdy to konieczne.

  • Powszechnym podejściem w poszukiwaniu ścieżek jest użycie współrzędnych istniejących obiektów do obliczeń, na przykład ustawiając cel ścieżki na pozycję istniejącego modelu TreasureChest w świecie. To podejście jest w pełni kompatybilne z Skryptami po stronie serwera, ponieważ serwer ma pełny widok świata w każdej chwili, lecz LokalneSkrypty i ModułoweSkrypty, które działają na kliencie, mogą się nie powieść, jeśli próbują obliczyć ścieżkę do obiektu, który nie jest strumieniowany.

    Aby zaradzić temu problemowi, rozważ ustawienie celu na pozycję BasePart w modelu persistent. Modele persistent ładują się wkrótce po dołączeniu gracza i nigdy się nie odłączają, więc skrypt po stronie klienta może połączyć się z zdarzeniem PersistentLoaded i bezpiecznie uzyskać dostęp do modelu, aby tworzyć waypointy po wywołaniu zdarzenia.

Ograniczenia i czynniki niepowodzenia

Silnik poszukiwania ścieżek ma określone ograniczenia, aby zapewnić efektywne przetwarzanie i optymalną wydajność. Dodatkowo, obliczenia poszukiwania ścieżek [m mogą nie powieść się z różnych przyczyn, jak opisano poniżej.

©2026 Roblox Corporation. Nazwa Roblox, logo Roblox oraz hasło „Powering Imagination” należą do naszych zarejestrowanych i niezarejestrowanych znaków towarowych na terenie Stanów Zjednoczonych oraz w innych krajach.