Nawigacja

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

Nawigacja to proces poruszania się postacią lub obiektem (agentem) wzdłuż logicznej ścieżki wokół przeszkód, aby dotrzeć do celu, opcjonalnie unikając materiałów niebezpiecznych lub zdefiniowanych obszarów.

Wizualizacja nawigacji

Aby pomóc w układzie i debugowaniu nawigacji, Studio może renderować siatkę nawigacyjną oraz etykiety modyfikatorów. Aby je włączyć, przełącz Siatkę nawigacyjną i Modyfikatory nawigacji w widżecie Opcje wizualizacji w prawym górnym rogu widoku 3D.

Zbliżenie na widok 3D z przyciskiem Opcje wizualizacji wskazanym w prawym górnym rogu.

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

Siatka nawigacyjna wyświetlana w Studio

Implementacja

Chociaż nawigacja może być implementowana na różne sposoby za pomocą PathfindingService i jego powiązanych metod, takich jak CreatePath(), ta sekcja używa następującego skryptu nawigacji 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 umieść go w 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, który jest skierowany w stronę 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)
    -- Uznaj waypoint za osiągnięty, jeśli jest bliżej 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ę celu z następnego waypointu w kierunku aktualnego
    currentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position
    -- Ustaw normalną 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 swoją własną spersonalizowaną 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 punkty na ścieżce
    waypoints = path:GetWaypoints()
    -- Wykryj, czy ścieżka została zablokowana
    blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
    -- Sprawdź, czy przeszkoda jest dalej wzdłuż ścieżki
    if blockedWaypointIndex >= nextWaypointIndex then
    -- Przestań wykrywać blokadę ścieżki, aż ścieżka zostanie ponownie obliczona
    blockedConnection:Disconnect()
    resetWaypointData()
    -- Wywołaj funkcję, aby ponownie obliczyć nową ścieżkę
    followPath()
    end
    end)
    findStartingPoint(waypoints)
    moveToNextWaypoint()
    end
    followPath()
  3. Edytuj zmienną DESTINATION (

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

  4. Przejdź przez następujące sekcje, aby dowiedzieć się o obliczaniu ścieżek i ruchu postaci.

Tworzenie ścieżki

Nawigacja jest inicjowana przez PathfindingService i jego metodę CreatePath() (

LINIE 9–14
). Metoda ta akceptuje opcjonalną tabelę parametrów, która precyzuje, jak postać (agent) porusza się wzdłuż ścieżki.

KluczOpisTypDomyślny
AgentRadiusPromień agenta, w studach. Przydatne do określenia minimalnego odstępu od przeszkód.integer2
AgentHeightWysokość agenta, w studach. Pusta przestrzeń mniejsza niż ta wartość, jak przestrzeń pod schodami, będzie oznaczona jako nieprzechodnia.integer5
AgentCanJumpOkreśla, czy skakanie podczas nawigacji jest dozwolone.booleantrue
AgentCanClimbOkreśla, czy wspinanie się po TrussParts podczas nawigacji jest dozwolone. Wspinaczka ma etykietę Label o nazwie Climb, a koszt wspinaczki wynosi domyślnie 1.booleanfalse
WaypointSpacingOdstęp między pośrednimi punktami na ścieżce. Jeśli ustawione na math.huge, nie będzie pośrednich punktów.number4
CostsTabela materiałów lub zdefiniowanych PathfindingModifiers i ich kosztów za przejście. Przydatne do sprawienia, że agent preferuje niektóre materiały/obszary nad innymi. Zobacz modyfikatory po szczegóły.tablenil

Obliczanie ścieżki

Po utworzeniu ważnej ścieżki za pomocą CreatePath(), musi być ona obliczona przez wywołanie Path:ComputeAsync() z Vector3 zarówno dla punktu startowego, jak i celu (

LINIE 133–139
).

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

Gdy Path jest obliczona, będzie zawierać szereg punktów nawigacyjnych, które śledzą ścieżkę od początku do końca. Punkty te można zebrać za pomocą metody Path:GetWaypoints() (

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

Punkty nawigacyjne wskazane wzdłuż obliczonej ścieżki
Punkty nawigacyjne wskazane wzdłuż obliczonej ścieżki

Ruch po ścieżce

Każdy PathWaypoint składa się zarówno z Position (Vector3), jak i Action (PathWaypointAction). Aby poruszyć postać zawierającą Humanoid, jak typowa postać Roblox, najlepszym sposobem jest wywołanie Humanoid:Move() z punktu nawigacyjnego do punktu nawigacyjnego i użycie zwrotu isCurrentWaypointReached() skryptu (

LINIE 32–56
) do wykrywania, kiedy postać osiąga każdy punkt nawigacyjny.

Zablokowane ścieżki

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

LINIE 145–154
).

Modyfikatory nawigacji

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

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

Aby jeszcze bardziej zoptymalizować nawigację, możesz wdrożyć modyfikatory nawigacji, aby obliczyć mądrzejsze ścieżki przez różne materiały, wokół zdefiniowanych obszarów lub aby ignorować przeszkody.

Koszty materiałów

Pracując z materiałami Terrain i BasePart, możesz dołączyć tabelę Costs w CreatePath(), aby uczynić niektóre materiały bardziej przejezdnymi niż inne. Wszystkie materiały mają domyślny koszt równy 1, a każdy materiał można zdefiniować jako nieprzechodni, ustawiając jego wartość na math.huge.

Klucze w tabeli Costs powinny być nazwami string 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 obszarów

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

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

    Zablokowana część definiująca obszar, do którego zastosowano modyfikator nawigacji.
  2. Wstaw instancję PathfindingModifier na część, znajdź jej właściwość Label i przypisz jej znaczącą 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 w 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
    }
    })

Ignorowanie przeszkód

W niektórych przypadkach przydatne jest, aby nawigacja przebiegała przez solidne przeszkody, jakby ich nie było. Pozwala to obliczyć ścieżkę przez konkretne fizyczne blokady, zamiast aby obliczenie nie powiodło się.

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

    Zablokowana część definiująca obszar, do którego zastosowano modyfikator nawigacji.
  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 rozciąga się poza drzwi i możesz nakazać zombie przejść przez nie. Nawet jeśli zombie nie jest w stanie otworzyć drzwi, reaguje tak, jakby "słyszało" postać za drzwiami.

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

Linki nawigacyjne

Czasami konieczne jest znalezienie ścieżki przez przestrzeń, która nie może być normalnie przebyta, na przykład przez przepaść, i wykonanie niestandardowej akcji, aby dotrzeć do następnego punktu nawigacyjnego. Można to osiągnąć za pomocą obiektu PathfindingLink.

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

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

Aby utworzyć PathfindingLink używając tego przykładu:

  1. OPCJONALNE
    Włącz Linki nawigacyjne w widżecie Opcje wizualizacji w prawym górnym rogu widoku 3D. Pomaga to w wizualizacji i debugowaniu podczas implementacji linków nawigacyjnych.

  2. Utwórz dwa Attachments, jeden na siedzeniu łodzi, a drugi w pobliżu punktu lądowania łodzi.

    Załączniki utworzone dla początku i końca linku nawigacyjnego.
  3. Utwórz obiekt PathfindingLink w przestrzeni roboczej, a następnie przypisz jego właściwości Attachment0 i Attachment1 do odpowiednio początkowych i końcowych załączników.

    Właściwości Attachment0/Attachment1 linku nawigacyjnego. Link nawigacyjny wizualizowany w świecie 3D.
  4. Przypisz znaczącą nazwę, taką jak UseBoat, do jej właściwości Label. Ta nazwa jest używana jako flaga w skrypcie nawigacyjnym do wywołania niestandardowej akcji, gdy agent osiągnie punkt początkowy linku.

    Właściwość Label określona dla PathfindingLink.
  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ść niższą 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, przemieścić łódź przez wodę, wysadzić agenta w punkcie lądowania łodzi, a następnie kontynuować ścieżkę agenta do jego ostatecznego celu.

Kompatybilność ze strumieniowaniem

Strumieniowanie instancji w grze streaming to potężna funkcja, która dynamicznie ładuje i odłącza zawartość 3D, gdy postać gracza porusza się po świecie. Gdy eksplorują przestrzeń 3D, nowe podzbiory przestrzeni strumieniują do ich urządzenia, a niektóre z istniejących podzbiorów mogą być odłączane.

Rozważ następujące najlepsze praktyki dotyczące używania PathfindingService w grach z włączonym strumieniowaniem:

  • Strumieniowanie może blokować lub odblokowywać daną ścieżkę, gdy postać porusza się wzdłuż niej. Na przykład, gdy postać biegnie przez las, drzewo może strumieniować gdzieś przed nimi i zablokować ścieżkę. Aby nawigacja działała płynnie ze strumieniowaniem, zdecydowanie zaleca się użycie techniki obsługi zablokowanych ścieżek i ponowne obliczenie ścieżki, gdy to konieczne.

  • Powszechnym podejściem w nawigacji jest użycie współrzędnych istniejących obiektów do obliczeń, na przykład ustawienie celu ścieżki na pozycję istniejącego modelu TreasureChest w świecie. To podejście jest w pełni zgodne z serwerowymi Scripts, ponieważ serwer ma pełny widok na świat przez cały czas, ale LocalScripts i ModuleScripts, które działają po stronie klienta, mogą nie powieść się, jeśli próbują obliczyć ścieżkę do obiektu, który nie jest strumieniowany.

    Aby rozwiązać ten problem, rozważ ustawienie celu na pozycję BasePart w modelu trwałym. Trwałe modele ładują się wkrótce po dołączeniu gracza i nigdy nie są odłączane, więc skrypt po stronie klienta może połączyć się z zdarzeniem PersistentLoaded i bezpiecznie uzyskać dostęp do modelu w celu utworzenia punktów nawigacyjnych po wywołaniu zdarzenia.

Ograniczenia i czynniki awarii

Silnik nawigacji ma określone ograniczenia, aby zapewnić efektywne przetwarzanie i optymalną wydajność. Dodatkowo, obliczenia nawigacji computations mogą nie powieść się z różnych powodów, 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.