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.

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.

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:
- WAŻNEW Eksploratorze wybierz kontener StarterPlayer. Następnie w oknie Właściwości ustaw zarówno DevComputerMovementMode, jak i DevTouchMovementMode na Scriptable.


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.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-- Oblicz odległość od humanoida do płaszczyzny celulocal dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance-- Oblicz komponent prędkości humanoida zmierzający w kierunku płaszczyznylocal velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)-- Oblicz próg od płaszczyzny celu na podstawie prędkości humanoidalocal 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 < thresholdelsereached = trueendif reached thencurrentWaypointPlaneNormal = Vector3.zerocurrentWaypointPlaneDistance = 0moveToNextWaypoint()endendlocal function calculateNextWaypointApproach()nextWaypointIndex += 1if nextWaypointIndex > #waypoints thenreturn falseendlocal currentWaypoint = waypoints[nextWaypointIndex - 1]local nextWaypoint = waypoints[nextWaypointIndex]-- Zbuduj płaszczyznę docelową z następnego waypointu w kierunku aktualnegocurrentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position-- Ustaw normalną na prostopadłą do płaszczyzny Y, gdy nie wspinamy sięif 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-- Wywołaj własną dostosowaną funkcję, aby sprawić, że agent użyje łodzielseif 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()-- Oblicz ścieżkępathfinderWorking = truewaitForGround()local success, errorMessage = pcall(function()path:ComputeAsync(character.PrimaryPart.Position, DESTINATION)end)if not success or path.Status ~= Enum.PathStatus.Success thenwarn("Ścieżka nie została obliczona!", errorMessage)returnend-- Pobierz waypoints ścieżkiwaypoints = path:GetWaypoints()-- Wykryj, czy ścieżka stała się zablokowanablockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)-- Sprawdź, czy przeszkoda znajduje się dalej na ścieżceif blockedWaypointIndex >= nextWaypointIndex then-- Przestań wykrywać zablokowanie ścieżki, dopóki nie obliczy się nowa ścieżkablockedConnection:Disconnect()resetWaypointData()-- Wywołaj funkcję, aby obliczyć nową ścieżkęfollowPath()endend)findStartingPoint(waypoints)moveToNextWaypoint()endfollowPath()Edytuj zmienną DESTINATION (
LINIA 5) na cel typu Vector3, do którego postać gracza może dotrzeć w świecie 3D.Przejdź через следующие разделы, чтобы узнать о вычислении пути и движении персонажа.
Tworzenie ścieżki
Poszukiwanie ścieżek inicjuje się za pomocą PathfindingService oraz metody CreatePath() (
| Klucz | Opis | Typ | Domyślnie |
|---|---|---|---|
| AgentRadius | Promień agenta, w studach. Przydatne do określenia minimalnego odstępu od przeszkód. | integer | 2 |
| AgentHeight | Wysokość agenta, w studach. Puste przestrzenie mniejsze niż ta wartość, jak przestrzeń pod schodami, będą oznaczone jako nieprzechodnie. | integer | 5 |
| AgentCanJump | Określa, czy skakanie podczas poszukiwania ścieżek jest dozwolone. | boolean | true |
| AgentCanClimb | Okreś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. | boolean | false |
| WaypointSpacing | Odległość między pośrednimi waypointami na ścieżce. Jeśli ustawione na math.huge, nie będzie pośrednich waypointów. | number | 4 |
| Costs | Tabela 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. | table | nil |
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 (

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() (

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

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.01local VELOCITY_MULTIPLIER = 0.0625local 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.
Stwórz część Anchored wokół regionu i ustaw jej właściwość CanCollide na false.

Wstaw instancję PathfindingModifier na część, zlokalizuj jej właściwość Label i przypisz znaczną nazwę, taką jak DangerZone.

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.01local VELOCITY_MULTIPLIER = 0.0625local 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.
Stwórz część Anchored wokół obiektu i ustaw jej właściwość CanCollide na false.

Wstaw instancję PathfindingModifier na część i włącz jej właściwość 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.

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.

Aby stworzyć PathfindingLink przy użyciu tego przykładu:
- OPTIONALWłą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.
Stwórz dwa Attachments, jeden na siedzeniu łodzi, a drugi blisko punktu lądowania łodzi.

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.


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.

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