路徑尋找是將角色或物體(代理)沿著邏輯路徑繞過障礙物以到達目的地的過程,並可選擇性地避免危險材料或定義的區域。
導航可視化
為了協助路徑尋找佈局和除錯,Studio 可以渲染導航網格和 修飾符 標籤。要啟用它們,請在 3D 視口的右上角的 可視化選項小工具中切換開啟 導航 網格 和 路徑尋找 修飾符。

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

實作
雖然路徑尋找可以通過 PathfindingService 及其相關方法如 CreatePath() 以多種方式實作,但本節使用以下路徑尋找腳本來處理玩家的角色。
在閱讀時進行測試:
- 重要在 資源管理器 中,選擇 StarterPlayer 容器。然後,在 屬性 窗口中,將 DevComputerMovementMode 和 DevTouchMovementMode 都設置為 Scriptable。


將以下代碼複製到 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.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-- 計算人形與目的地平面的距離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 < thresholdelsereached = trueendif reached thencurrentWaypointPlaneNormal = Vector3.zerocurrentWaypointPlaneDistance = 0moveToNextWaypoint()endendlocal function calculateNextWaypointApproach()nextWaypointIndex += 1if nextWaypointIndex > #waypoints thenreturn falseendlocal currentWaypoint = waypoints[nextWaypointIndex - 1]local nextWaypoint = waypoints[nextWaypointIndex]-- 從下一個路徑點到當前路徑點建立目的地平面currentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position-- 當不向上攀爬時,設置法線垂直於 Y 平面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-- 調用您自己的自定義函數使代理使用船elseif 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()-- 計算路徑pathfinderWorking = truewaitForGround()local success, errorMessage = pcall(function()path:ComputeAsync(character.PrimaryPart.Position, DESTINATION)end)if not success or path.Status ~= Enum.PathStatus.Success thenwarn("路徑未計算!", errorMessage)returnend-- 獲取路徑路徑點waypoints = path:GetWaypoints()-- 檢測路徑是否被阻塞blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)-- 檢查障礙物是否在路徑的更遠處if blockedWaypointIndex >= nextWaypointIndex then-- 停止檢測路徑阻塞,直到重新計算路徑blockedConnection:Disconnect()resetWaypointData()-- 調用函數重新計算新路徑followPath()endend)findStartingPoint(waypoints)moveToNextWaypoint()endfollowPath()繼續閱讀以下各節以了解路徑計算和角色移動。
路徑創建
路徑尋找是通過 PathfindingService 和其 CreatePath() 方法啟動的 (
| 鍵 | 描述 | 類型 | 默認值 |
|---|---|---|---|
| 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 來 計算 它 (

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

路徑移動
每個 PathWaypoint 由 Position (Vector3) 和 Action (PathWaypointAction) 組成。要移動包含 Humanoid 的角色,如典型的 Roblox 角色,最佳方法是從路徑點到路徑點調用 Humanoid:Move(),並使用腳本的 isCurrentWaypointReached() 回調 (
被阻塞的路徑
許多 Roblox 世界是動態的;部件可能移動或掉落,地板可能會崩塌。這可能會阻塞計算出的路徑,並阻止角色到達其目的地。為了處理這種情況,您可以連接 Path.Blocked 事件並重新計算路徑以繞過阻塞的部分 (
路徑尋找修飾符
默認情況下,Path:ComputeAsync() 返回起點和目的地之間的 最短 路徑,例外情況是它會嘗試避免跳躍。在某些情況下,這看起來不自然;例如,路徑可能會穿過沼澤水而不是繞過它,僅僅因為穿過水的路徑在幾何上更短。

為了進一步優化路徑尋找,您可以實作 路徑尋找修飾符 以計算更智能的路徑,穿越各種 材料、繞過定義的 區域,或 忽略障礙物。
材料成本
在處理 Terrain 和 BasePart 材料時,您可以在 CreatePath() 中包含一個 Costs 表,以使某些材料比其他材料更易於通行。所有材料的默認成本為 1,任何材料都可以通過將其值設置為 math.huge 來定義為不可通行。
Costs 表中的鍵應為 字符串 名稱,表示 Enum.Material 名稱,例如 Water 代表 Enum.Material.Water 或 CrackedLava 代表 Enum.Material.CrackedLava。
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 對象添加到部件來實現。
在區域周圍創建一個 Anchored 部件,並將其 CanCollide 屬性設置為 false。

在部件上插入一個 PathfindingModifier 實例,找到其 Label 屬性,並分配一個有意義的名稱,如 DangerZone。

在 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.01local VELOCITY_MULTIPLIER = 0.0625local path = PathfindingService:CreatePath({AgentCanClimb = true,Costs = {DangerZone = math.huge, Water = 20, CrackedLava = 20, Slate = 20}})
忽略障礙物
在某些情況下,通過實體障礙物進行路徑尋找是有用的,就好像它們不存在一樣。這使您能夠計算通過特定物理阻擋者的路徑,而不是計算直接失敗。
在物體周圍創建一個 Anchored 部件,並將其 CanCollide 屬性設置為 false。

在部件上插入一個 PathfindingModifier 實例,並啟用其 PassThrough 屬性。

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

路徑尋找鏈接
有時需要在無法正常通行的空間中找到路徑,例如跨越深淵,並執行自定義操作以到達下一個路徑點。這可以通過 PathfindingLink 對象來實現。
使用上述示例,您可以讓代理使用船。

要使用此示例創建 PathfindingLink:
在船的座位上和船的著陸點附近創建兩個 Attachments。

為其 Label 屬性分配一個有意義的名稱,如 UseBoat。此名稱用作路徑尋找腳本中的標誌,以在代理到達起始鏈接點時觸發自定義操作。

在 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.01local VELOCITY_MULTIPLIER = 0.0625local path = PathfindingService:CreatePath({AgentCanClimb = true,Costs = {UseBoat = 2, Water = 20}})在 moveToNextWaypoint() 函數中 (
第 93–114 行),可以使用對 Label 修飾符名稱的自定義檢查來執行與 Humanoid:Move() 不同的操作;在這種情況下,您可能會調用一個函數來讓代理坐在船上,將船移動到水中,在船的著陸點讓代理下船,然後繼續代理的路徑到達最終目的地。
串流兼容性
遊戲中的 實例串流 是一個強大的功能,能夠在玩家角色在世界中移動時動態加載和卸載 3D 內容。當他們探索 3D 空間時,新的子集會串流到他們的設備上,而一些現有的子集可能會被卸載。
考慮在啟用串流的遊戲中使用 PathfindingService 的以下最佳實踐:
串流可以在角色沿著路徑移動時阻塞或解除阻塞給定的路徑。例如,當角色穿過森林時,樹木可能會在他們前方的某處串流進來並阻礙路徑。為了使路徑尋找與串流無縫協作,強烈建議您使用 處理被阻塞的路徑 技術,並在必要時重新計算路徑。
路徑尋找中的一種常見方法是使用現有物體的坐標進行 計算,例如將路徑目的地設置為世界中現有的 TreasureChest 模型的位置。這種方法與伺服器端的 Scripts 完全兼容,因為伺服器始終對世界有完整的視圖,但在客戶端運行的 LocalScripts 和 ModuleScripts 如果嘗試計算到未串流進來的物體的路徑可能會失敗。
為了解決此問題,考慮將目的地設置為 持久 模型中 BasePart 的位置。持久模型在玩家加入後不久加載,並且永遠不會被卸載,因此客戶端腳本可以連接到 PersistentLoaded 事件,並在事件觸發後安全地訪問模型以創建路徑點。
限制和失敗因素
路徑尋找引擎包含特定的限制,以確保高效處理和最佳性能。此外,路徑尋找 計算 可能因各種原因而失敗,如下所述。

