路徑尋找

*此內容是使用 AI(Beta 測試版)翻譯,可能含有錯誤。若要以英文檢視此頁面,請按一下這裡。

路徑尋找是將角色或物體(代理)沿著邏輯路徑繞過障礙物以到達目的地的過程,並可選擇性地避免危險材料或定義的區域。

導航可視化

為了協助路徑尋找佈局和除錯,Studio 可以渲染導航網格和 修飾符 標籤。要啟用它們,請在 3D 視口的右上角的 可視化選項小工具中切換開啟 導航 網格 和 路徑尋找 修飾符。

3D 視口的特寫視圖,右上角顯示可視化選項按鈕。

啟用 導航網格 後,彩色區域顯示角色可能行走或游泳的地方。小箭頭指示角色將通過跳躍嘗試到達的區域。

在 Studio 中顯示的導航網格

實作

雖然路徑尋找可以通過 PathfindingService 及其相關方法如 CreatePath() 以多種方式實作,但本節使用以下路徑尋找腳本來處理玩家的角色。

在閱讀時進行測試:

  1. 重要
    在 資源管理器 中,選擇 StarterPlayer 容器。然後,在 屬性 窗口中,將 DevComputerMovementMode 和 DevTouchMovementMode 都設置為 Scriptable。

  2. 將以下代碼複製到 LocalScript 中,放在 StarterCharacterScripts 內,或 獲取此包 並將其放入 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
    -- 計算人形與目的地平面的距離
    local dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance
    -- 計算人形速度朝向平面的分量
    local velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)
    -- 根據人形速度計算距離平面的閾值
    local threshold = math.max(1.0, VELOCITY_MULTIPLIER * velocity)
    -- 如果距離平面前方小於閾值則視為到達
    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]
    -- 從下一個路徑點到當前路徑點建立目的地平面
    currentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position
    -- 當不向上攀爬時,設置法線垂直於 Y 平面
    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
    -- 調用您自己的自定義函數使代理使用船
    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()
    -- 計算路徑
    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("路徑未計算!", errorMessage)
    return
    end
    -- 獲取路徑路徑點
    waypoints = path:GetWaypoints()
    -- 檢測路徑是否被阻塞
    blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
    -- 檢查障礙物是否在路徑的更遠處
    if blockedWaypointIndex >= nextWaypointIndex then
    -- 停止檢測路徑阻塞,直到重新計算路徑
    blockedConnection:Disconnect()
    resetWaypointData()
    -- 調用函數重新計算新路徑
    followPath()
    end
    end)
    findStartingPoint(waypoints)
    moveToNextWaypoint()
    end
    followPath()
  3. 編輯 DESTINATION 變數 (

    第 5 行
    ) 為玩家角色可以到達的 3D 世界中的 Vector3 目的地。

  4. 繼續閱讀以下各節以了解路徑計算和角色移動。

路徑創建

路徑尋找是通過 PathfindingService 和其 CreatePath() 方法啟動的 (

第 9–14 行
)。此方法接受一個可選的參數表,以微調角色(代理)沿路徑移動的方式。

鍵描述類型默認值
AgentRadius代理半徑,以 studs 為單位。用於確定與障礙物的最小距離。整數2
AgentHeight代理高度,以 studs 為單位。小於此值的空間,如樓梯下的空間,將被標記為不可通行。整數5
AgentCanJump確定在路徑尋找過程中是否允許跳躍。布林值true
AgentCanClimb確定在路徑尋找過程中是否允許攀爬 TrussParts。可攀爬的路徑具有名為 Climb 的 Label,並且可攀爬路徑的 成本 默認為 1。布林值false
WaypointSpacing路徑中中間路徑點之間的間距。如果設置為 math.huge,則不會有中間路徑點。數字4
Costs材料或定義的 PathfindingModifiers 的表格及其通行成本。用於使代理更喜歡某些材料/區域而非其他材料。詳情請參見 修飾符。表格nil

路徑計算

在您使用 CreatePath() 創建有效路徑後,必須通過調用 Path:ComputeAsync() 並為起點和目的地提供 Vector3 來 計算 它 (

第 133–139 行
)。

標記在兩座橋上的路徑起點/終點

一旦計算出 Path,它將包含一系列從起點到終點的 路徑點。這些點可以通過 Path:GetWaypoints() 方法收集 (

第 142 行
)。返回的數組按路徑從起點到終點的順序排列。

在計算路徑上指示的路徑點
在計算路徑上指示的路徑點

路徑移動

每個 PathWaypoint 由 Position (Vector3) 和 Action (PathWaypointAction) 組成。要移動包含 Humanoid 的角色,如典型的 Roblox 角色,最佳方法是從路徑點到路徑點調用 Humanoid:Move(),並使用腳本的 isCurrentWaypointReached() 回調 (

第 32–56 行
) 來檢測角色何時到達每個路徑點。

被阻塞的路徑

許多 Roblox 世界是動態的;部件可能移動或掉落,地板可能會崩塌。這可能會阻塞計算出的路徑,並阻止角色到達其目的地。為了處理這種情況,您可以連接 Path.Blocked 事件並重新計算路徑以繞過阻塞的部分 (

第 145–154 行
)。

路徑尋找修飾符

默認情況下,Path:ComputeAsync() 返回起點和目的地之間的 最短 路徑,例外情況是它會嘗試避免跳躍。在某些情況下,這看起來不自然;例如,路徑可能會穿過沼澤水而不是繞過它,僅僅因為穿過水的路徑在幾何上更短。

兩條路徑,較短的路徑不一定更合邏輯

為了進一步優化路徑尋找,您可以實作 路徑尋找修飾符 以計算更智能的路徑,穿越各種 材料、繞過定義的 區域,或 忽略障礙物。

材料成本

在處理 Terrain 和 BasePart 材料時,您可以在 CreatePath() 中包含一個 Costs 表,以使某些材料比其他材料更易於通行。所有材料的默認成本為 1,任何材料都可以通過將其值設置為 math.huge 來定義為不可通行。

Costs 表中的鍵應為 字符串 名稱,表示 Enum.Material 名稱,例如 Water 代表 Enum.Material.Water 或 CrackedLava 代表 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
}
})

配置區域

在某些情況下, 材料偏好 不夠。例如,您可能希望角色避免定義的 區域,無論腳下的材料如何。這可以通過將 PathfindingModifier 對象添加到部件來實現。

  1. 在區域周圍創建一個 Anchored 部件,並將其 CanCollide 屬性設置為 false。

    定義應用路徑尋找修飾符的區域的固定部件。
  2. 在部件上插入一個 PathfindingModifier 實例,找到其 Label 屬性,並分配一個有意義的名稱,如 DangerZone。

    PathfindingModifier 實例,其 Label 屬性設置為 DangerZone。
  3. 在 CreatePath() 的 Costs 表中包含一個匹配的 DangerZone 鍵和相關的數值。通過將其值設置為 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
    }
    })

忽略障礙物

在某些情況下,通過實體障礙物進行路徑尋找是有用的,就好像它們不存在一樣。這使您能夠計算通過特定物理阻擋者的路徑,而不是計算直接失敗。

  1. 在物體周圍創建一個 Anchored 部件,並將其 CanCollide 屬性設置為 false。

    定義應用路徑尋找修飾符的區域的固定部件。
  2. 在部件上插入一個 PathfindingModifier 實例,並啟用其 PassThrough 屬性。

    PathfindingModifier 實例,其 PassThrough 屬性已啟用。

    現在,當從殭屍 NPC 到玩家角色計算路徑時,路徑會延伸到門之外,您可以提示殭屍穿過它。即使殭屍無法打開門,它也會像是“聽到”門後的角色一樣做出反應。

    殭屍 NPC 路徑穿過先前阻擋的門。

路徑尋找鏈接

有時需要在無法正常通行的空間中找到路徑,例如跨越深淵,並執行自定義操作以到達下一個路徑點。這可以通過 PathfindingLink 對象來實現。

使用上述示例,您可以讓代理使用船。

PathfindingLink 顯示代理如何使用船。

要使用此示例創建 PathfindingLink:

  1. 可選
    在 3D 視口的右上角的 可視化選項 小工具中切換開啟 路徑尋找 鏈接。這有助於在實作路徑尋找鏈接時進行可視化和除錯。

  2. 在船的座位上和船的著陸點附近創建兩個 Attachments。

    為路徑尋找鏈接的起點和終點創建的附件。
  3. 在工作區中創建一個 PathfindingLink 對象,然後將其 Attachment0 和 Attachment1 屬性分別分配給起始和結束附件。

    PathfindingLink 的 Attachment0/Attachment1 屬性。 在 3D 世界中可視化的 PathfindingLink。
  4. 為其 Label 屬性分配一個有意義的名稱,如 UseBoat。此名稱用作路徑尋找腳本中的標誌,以在代理到達起始鏈接點時觸發自定義操作。

    為 PathfindingLink 指定的 Label 屬性。
  5. 在 CreatePath() 中包含一個 Costs 表,其中包含 Water 鍵和與 Label 屬性名稱匹配的自定義鍵。將自定義鍵的值設置為 低於 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. 在 moveToNextWaypoint() 函數中 (

    第 93–114 行
    ),可以使用對 Label 修飾符名稱的自定義檢查來執行與 Humanoid:Move() 不同的操作;在這種情況下,您可能會調用一個函數來讓代理坐在船上,將船移動到水中,在船的著陸點讓代理下船,然後繼續代理的路徑到達最終目的地。

串流兼容性

遊戲中的 實例串流 是一個強大的功能,能夠在玩家角色在世界中移動時動態加載和卸載 3D 內容。當他們探索 3D 空間時,新的子集會串流到他們的設備上,而一些現有的子集可能會被卸載。

考慮在啟用串流的遊戲中使用 PathfindingService 的以下最佳實踐:

  • 串流可以在角色沿著路徑移動時阻塞或解除阻塞給定的路徑。例如,當角色穿過森林時,樹木可能會在他們前方的某處串流進來並阻礙路徑。為了使路徑尋找與串流無縫協作,強烈建議您使用 處理被阻塞的路徑 技術,並在必要時重新計算路徑。

  • 路徑尋找中的一種常見方法是使用現有物體的坐標進行 計算,例如將路徑目的地設置為世界中現有的 TreasureChest 模型的位置。這種方法與伺服器端的 Scripts 完全兼容,因為伺服器始終對世界有完整的視圖,但在客戶端運行的 LocalScripts 和 ModuleScripts 如果嘗試計算到未串流進來的物體的路徑可能會失敗。

    為了解決此問題,考慮將目的地設置為 持久 模型中 BasePart 的位置。持久模型在玩家加入後不久加載,並且永遠不會被卸載,因此客戶端腳本可以連接到 PersistentLoaded 事件,並在事件觸發後安全地訪問模型以創建路徑點。

限制和失敗因素

路徑尋找引擎包含特定的限制,以確保高效處理和最佳性能。此外,路徑尋找 計算 可能因各種原因而失敗,如下所述。

©2026 Roblox Corporation、Roblox、Roblox 標誌及 Powering Imagination 是我們在美國及其他國家地區的部分註冊與未註冊商標。