寻路

*此内容使用人工智能(Beta)翻译,可能包含错误。若要查看英文页面,请点按 此处

寻路是沿着逻辑路径移动角色或对象(代理)以绕过障碍物到达目的地的过程,可选地避免有害材料或定义区域。

导航可视化

为了帮助路径布局和调试,Studio可以渲染导航网格和 修饰符 标签。要启用它们,请在3D视口的右上角从 可视化选项 小部件中切换 导航 网格寻路 修饰符

3D视口的特写视图,右上角标示了可视化选项按钮。

启用 导航网格 后,彩色区域显示角色可能行走或游泳的位置。小箭头指示角色将通过跳跃尝试到达的区域。

Studio中显示的导航网格

实现

虽然寻路可以通过 PathfindingService 和其关联的方法(如 CreatePath())以各种方式实现,但本节使用以下脚本为角色进行寻路。

在阅读时进行测试:

  1. 重要
    资源管理器 中选择 StarterPlayer 容器。然后在 属性 窗口中,将 DevComputerMovementModeDevTouchMovementMode 两者都设置为 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代理半径,单位以Stud为单位。用于确定与障碍物之间的最小间隔。整数2
AgentHeight代理高度,单位以Stud为单位。小于此值的空隙(例如楼梯下方的空间)将被标记为不可通行。整数5
AgentCanJump确定在寻路过程中是否允许跳跃。布尔值true
AgentCanClimb确定在寻路过程中是否允许攀爬 TrussParts。可攀爬的路径有一个名为 ClimbLabel 标签,和 费用 默认值为 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() 返回起点和目的地之间的 最短 路径,但它会尝试避免跳跃。这在某些情况下看起来很不自然;例如,路径可能通过沼泽水而不是绕过它,仅仅因为通过水的路径在几何上更短。

两条路径展示,较短的路径不一定更合理

为了进一步优化寻路,您可以实现 寻路修饰符 来计算跨越各种 材料、围绕定义 区域忽略障碍 的更智能路径。

材料费用

在处理 TerrainBasePart 材料时,您可以在 CreatePath() 中包含一个 Costs 表,以使某些材料比其他材料更易通行。所有材料的默认费用为 1,任何材料都可以通过将其值设置为 math.huge 来定义为不可通行。

Costs 表中的键应该是 字符串 名称,表示 Enum.Material 名称,例如 Water 表示 Enum.Material.WaterCrackedLava 表示 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 对象,然后将其 Attachment0Attachment1 属性分别分配给起始和结束附件。

    PathfindingLink 的 Attachment0/Attachment1 属性。 在3D世界中可视化的 PathfindingLink。
  4. 将有意义的名称(如 UseBoat)分配给其 Label 属性。此名称在路径寻找脚本中用作标志,以在代理到达起始链接点时触发自定义操作。

    为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 完全兼容,因为服务器始终完全了解世界的情况,但在客户端运行的 LocalScriptsModuleScripts 如果尝试计算到未流入的对象的路径,则可能会失败。

    为解决此问题,请考虑将目的地设置为 持久性 模型中 BasePart 的位置。持久性模型在玩家加入后将很快加载,并且永远不会流出,因此客户端脚本可以连接到 PersistentLoaded 事件,在事件触发后安全访问模型以创建路点。

限制和失败因素

寻路引擎包含特定限制以确保高效处理和最佳性能。此外,寻路的 计算 可能因各种原因失败,如下所示。

©2026 Roblox Corporation、Roblox、Roblox 标志及 Powering Imagination 是我们在美国及其他国家或地区的注册与未注册商标。