角色路路径查找

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

路径探索 是将角色移动到逻辑路径上以达到目的地的过程,避免障碍物和(可选)危险物质或定义区域。

导航视觉化

为了帮助路径拓扑布置和调试,Studio 可以渲染导航网格和 调整器 标签。要启用它们,请在 3D 视图右上角的 视觉选项 选项卡中切换 导航网格路径找到修改器 ,以启用它们。

A close up view of the 3D viewport with the Visualization Options button indicated in the upper-right corner.

启用了 导航网格 后,彩色区域显示角色可能走路或游泳的地方,而非彩色区域被阻止。小箭头表示角色可以通过跳跃尝试到达的区域,假设你在创建路径时将 设置为 ,当 创建路径时。

Navigation mesh showing in Studio

启用了 路径匹配修改器 ,文本标签表示在使用路径匹配修改器时考虑的特定材料和区域。

Navigation labels showing on navigation mesh

已知限制

路径探索功能具有特定限制,以确保高效处理和最佳性能。

垂直放置限制

路径找到计算只考虑特定垂直边界内的零件:

  • 下限 — 部件的底部 Y 坐标小于 -65,536 格,将被忽略。
  • 上限 — 拥有顶部 Y 坐标超过 65,536 个螺柱的零件被忽略。
  • 垂直距离 — 从最低部分底部 Y 坐标到最高部分顶部 Y 坐标的垂直距离不得超过 65,536 个螺柱;否则,路径拓扑系统在路径计算时将忽略这些部分。

搜索距离限制

从起始点到终点的路径拓展直接视觉距离不得超过 3,000 个单位。超过此距离会导致NoPath状态。

创建路径

路径探索通过 PathfindingService 和其 CreatePath() 函数启动。

本地脚本

local PathfindingService = game:GetService("PathfindingService")
local path = PathfindingService:CreatePath()

CreatePath() 接受一个可选的参数表,该参数表可调整角色(代理)沿路移动的方式。

关键描述类型默认
AgentRadius代理半径,以点为单位。有用于确定最小与障碍物分离的距离。整数2
AgentHeight代理高度,以学分为单位。小于此值的空间,例如楼梯下的空间,将被标记为不可穿越。整数5
AgentCanJump决定是否允许在路径探索期间跳跃。boolean 类型true
AgentCanClimb决定是否允许在路径探索期间攀登TrussPartsboolean 类型false
WaypointSpacing路径中的中间路径点之间的间隔。如果设置为 math.huge,将不存在中间路径点。数字4
Costs材料表或定义的PathfindingModifiers和其通过成本。有助于使代理更喜欢某些材料/区域而不是其他材料/区域。见 调整因素 获取详细信息。nil
本地脚本

local PathfindingService = game:GetService("PathfindingService")
local path = PathfindingService:CreatePath({
AgentRadius = 3,
AgentHeight = 6,
AgentCanJump = false,
Costs = {
Water = 20
}
})

请注意,在路径拓扑期间,代理可以攀爬 ,假设你将 设置为 ,当创建路径时,没有任何东西阻止代理从桁架攀爬路径。攀爬路径具有 攀爬 标签,攀爬路径的 成本 默认为 1

Path going up a climbable TrussPart ladder
本地脚本 - 桁架攀登路径

local PathfindingService = game:GetService("PathfindingService")
local path = PathfindingService:CreatePath({
AgentCanClimb = true,
Costs = {
Climb = 2 -- 攀登路径的成本;默认值为 1
}
})

沿路移动

本节使用以下路径抓取脚本为玩家角色。要在阅读时测试:

  1. 将代码复制到 LocalScript 内的 StarterCharacterScripts 中。
  2. 将 变量设置为你的 3D 世界中的一个目的地,玩家角色可以到达。
  3. 通过以下部分继续学习路径计算和角色移动。
本地脚本 - 角色路径匹配

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local path = PathfindingService:CreatePath()
local player = Players.LocalPlayer
local character = player.Character
local humanoid = character:WaitForChild("Humanoid")
local TEST_DESTINATION = Vector3.new(100, 0, 100)
local waypoints
local nextWaypointIndex
local reachedConnection
local blockedConnection
local function followPath(destination)
-- 计算路径
local success, errorMessage = pcall(function()
path:ComputeAsync(character.PrimaryPart.Position, destination)
end)
if success and path.Status == Enum.PathStatus.Success then
-- 获取路径路径点
waypoints = path:GetWaypoints()
-- 检测路径是否被阻塞
blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
-- 检查障碍物是否在路径下方
if blockedWaypointIndex >= nextWaypointIndex then
-- 在路径重计算后停止检测路径阻塞
blockedConnection:Disconnect()
-- 调用函数重新计算新路径
followPath(destination)
end
end)
-- 检测当前移动到下一个路径点完成时
if not reachedConnection then
reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
if reached and nextWaypointIndex < #waypoints then
-- 增加路径索引并移至下一个路径点
nextWaypointIndex += 1
humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
else
reachedConnection:Disconnect()
blockedConnection:Disconnect()
end
end)
end
-- 初始移动到第二个路径点(第一个路径点是路径开始;跳过它)
nextWaypointIndex = 2
humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
else
warn("Path not computed!", errorMessage)
end
end
followPath(TEST_DESTINATION)

计算路径

在您创建有效路径 CreatePath() 之后,必须通过调用 **** 来计算 Path:ComputeAsync() 对起始点和目的地的 Vector3

本地脚本 - 角色路径匹配

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local path = PathfindingService:CreatePath()
local player = Players.LocalPlayer
local character = player.Character
local humanoid = character:WaitForChild("Humanoid")
local TEST_DESTINATION = Vector3.new(100, 0, 100)
local waypoints
local nextWaypointIndex
local reachedConnection
local blockedConnection
local function followPath(destination)
-- 计算路径
local success, errorMessage = pcall(function()
path:ComputeAsync(character.PrimaryPart.Position, destination)
end)
end
Path start/end marked on series of islands and bridges

获取路径点

一旦 Path 计算完成,它将包含一系列 路径节点 ,可以追踪从开始到结束的路径。这些点可以用 Path:GetWaypoints() 函数收集。

本地脚本 - 角色路径匹配

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local path = PathfindingService:CreatePath()
local player = Players.LocalPlayer
local character = player.Character
local humanoid = character:WaitForChild("Humanoid")
local TEST_DESTINATION = Vector3.new(100, 0, 100)
local waypoints
local nextWaypointIndex
local reachedConnection
local blockedConnection
local function followPath(destination)
-- 计算路径
local success, errorMessage = pcall(function()
path:ComputeAsync(character.PrimaryPart.Position, destination)
end)
if success and path.Status == Enum.PathStatus.Success then
-- 获取路径路径点
waypoints = path:GetWaypoints()
end
end
Waypoints indicated across computed path
指示计算路径的路径点

路径移动

每个路径点包括一个 位置 ( Vector3 ) 和一个 行动 ( PathWaypointAction )。要移动包含 Humanoid 的角色,例如典型 Roblox 角色,最简单的方法是从路径点到路径点调用 Humanoid:MoveTo() ,使用 MoveToFinished 事件检测角色何时到达每个路径点。

本地脚本 - 角色路径匹配

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local path = PathfindingService:CreatePath()
local player = Players.LocalPlayer
local character = player.Character
local humanoid = character:WaitForChild("Humanoid")
local TEST_DESTINATION = Vector3.new(100, 0, 100)
local waypoints
local nextWaypointIndex
local reachedConnection
local blockedConnection
local function followPath(destination)
-- 计算路径
local success, errorMessage = pcall(function()
path:ComputeAsync(character.PrimaryPart.Position, destination)
end)
if success and path.Status == Enum.PathStatus.Success then
-- 获取路径路径点
waypoints = path:GetWaypoints()
-- 检测路径是否被阻塞
blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
-- 检查障碍物是否在路径下方
if blockedWaypointIndex >= nextWaypointIndex then
-- 在路径重计算后停止检测路径阻塞
blockedConnection:Disconnect()
-- 调用函数重新计算新路径
followPath(destination)
end
end)
-- 检测当前移动到下一个路径点完成时
if not reachedConnection then
reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
if reached and nextWaypointIndex < #waypoints then
-- 增加路径索引并移至下一个路径点
nextWaypointIndex += 1
humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
else
reachedConnection:Disconnect()
blockedConnection:Disconnect()
end
end)
end
-- 初始移动到第二个路径点(第一个路径点是路径开始;跳过它)
nextWaypointIndex = 2
humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
else
warn("Path not computed!", errorMessage)
end
end

处理被阻止的路径

许多 Roblox 世界是动态的;零件可能会移动或掉落,地板可能会坍塌。这可以阻止计算出的路径并防止角色达到目的地。为了处理这一问题,您可以连接 Path.Blocked 事件并重新计算阻止它的路径。

本地脚本 - 角色路径匹配

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local path = PathfindingService:CreatePath()
local player = Players.LocalPlayer
local character = player.Character
local humanoid = character:WaitForChild("Humanoid")
local TEST_DESTINATION = Vector3.new(100, 0, 100)
local waypoints
local nextWaypointIndex
local reachedConnection
local blockedConnection
local function followPath(destination)
-- 计算路径
local success, errorMessage = pcall(function()
path:ComputeAsync(character.PrimaryPart.Position, destination)
end)
if success and path.Status == Enum.PathStatus.Success then
-- 获取路径路径点
waypoints = path:GetWaypoints()
-- 检测路径是否被阻塞
blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
-- 检查障碍物是否在路径下方
if blockedWaypointIndex >= nextWaypointIndex then
-- 在路径重计算后停止检测路径阻塞
blockedConnection:Disconnect()
-- 调用函数重新计算新路径
followPath(destination)
end
end)
end
end

路径找到修改器

默认情况下,Path:ComputeAsync() 返回起始点和目的地之间的 最短路径 ,除了它试图避免跳跃。在某些情况下,这看起来不自然——例实例,一条路可能会穿过水而不是穿过附近的桥,仅因为穿过水的路径更短。

Two paths indicated with the shorter path not necessarily more logical

为了进一步优化路径探索,您可以通过实现 路径修改器 来计算更聪明的路径通过各种材料、环绕定义的区域或通过障碍物来。

设置材料成本

当与 TerrainBasePart 材料工作时,您可以在 Costs 内包含一张 CreatePath() 表,以使某些材料比其他材料更容易穿越。所有材料的默认成本为 1 ,任何材料都可以通过设置其值为 math.huge 来定义为不可穿越。

Costs 表中的键应为代表 Enum.Material 名称的字符串名称,例如 WaterEnum.Material.Water .

本地脚本 - 角色路径匹配

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local path = PathfindingService:CreatePath({
Costs = {
Water = 20,
Mud = 5,
Neon = math.huge
}
})

与区域合作

在某些情况下,材料偏好不足以满足需求。例如,你可能希望角色避免 定义的区域 ,无论是否有材料在脚下。这可以通过将 PathfindingModifier 对象添加到零件来实现。

  1. 在危险区域周围创建一个 Anchored 部分,将其 CanCollide 属性设置为 false

    Anchored part defining a region to apply a pathfinding modifier to
  2. PathfindingModifier 实例插入零件,找到其 Label 属性,然后分配一个有意义的名称,例如 危险区

    PathfindingModifier instance with Label property set to DangerZone
  3. 包含一个含有匹配键和相关数值的 Costs 表在 CreatePath() 内,其中包含匹配的键。修改器可以通过将其值设置为 math.huge 来定义为不可遍历。

    本地脚本 - 角色路径匹配

    local PathfindingService = game:GetService("PathfindingService")
    local Players = game:GetService("Players")
    local RunService = game:GetService("RunService")
    local Workspace = game:GetService("Workspace")
    local path = PathfindingService:CreatePath({
    Costs = {
    DangerZone = math.huge
    }
    })

忽略障碍物

在某些情况下,通过假装没有障碍物来寻找路径是有用的。这可以让你计算特定物理障碍的路径,而不是计算完全失败。

  1. 在对象周围创建一个 Anchored 部分,并将其 CanCollide 属性设置为 false

    Anchored part defining a region to apply a pathfinding modifier to
  2. PathfindingModifier 实例插入零件并启用其 PassThrough 属性。

    PathfindingModifier instance with PassThrough property enabled

    现在,当从僵尸 NPC 到玩家角色的路径计算时,路径会超出门,你可以向僵尸提示通过它。即使僵尸无法打开门,它也像是“听到”了门后面的角色。

    Zombie NPC path passing through the previously blocking door

寻路链接

有时需要找到一个通过无法正常穿越的空间的路径,例如穿过峡谷,然后执行自定义行动来到达下一个路径点。这可以通过 PathfindingLink 对象实现。

使用上面的岛屿示例,您可以让代理使用船而不是穿过所有桥梁来行走。

PathfindingLink showing how an agent can use a boat instead of walking across all of the bridges

要使用此例子创建一个 PathfindingLink

  1. 要帮助进行可视化和调试,请在 3D 视图右上角的 视觉选项 选项卡中切换 路径拓扑链接 ,从而显示路径。

  2. 创建两个 Attachments , 一个在船的座位上, 一个靠近船的起落点。

    Attachments created for pathfinding link's start and end
  3. 在工作区中创建一个 PathfindingLink 对象,然后将其 附件0附件1 属性分别分配给开始和结束附件。

    Attachment0/Attachment1 properties of a PathfindingLink PathfindingLink visualized in the 3D world
  4. 将具有意义的名称 like UseBoat 分配到其 Label 属性。该名称在路径抓取脚本中用作旗帜,当代理达到起始链接点时触发自定义行动。

    Label property specified for PathfindingLink
  5. 包含一个 表,内含 both a 键和一个匹配 属性名称的自定义键。为自定义键分配低于 Water 的值。

    本地脚本 - 角色路径匹配

    local PathfindingService = game:GetService("PathfindingService")
    local Players = game:GetService("Players")
    local RunService = game:GetService("RunService")
    local Workspace = game:GetService("Workspace")
    local path = PathfindingService:CreatePath({
    Costs = {
    Water = 20,
    UseBoat = 1
    }
    })
  6. 在达到路径节点时触发的事件中,添加自定义检查到 Label 修改器名称,并采取与 Humanoid:MoveTo() 不同的行动——在这种情况下,调用函数将代理安装在船上,将船移到水上,然后在到达目的地岛时继续代理的路径。

    本地脚本 - 角色路径匹配

    local PathfindingService = game:GetService("PathfindingService")
    local Players = game:GetService("Players")
    local RunService = game:GetService("RunService")
    local Workspace = game:GetService("Workspace")
    local path = PathfindingService:CreatePath({
    Costs = {
    Water = 20,
    UseBoat = 1
    }
    })
    local player = Players.LocalPlayer
    local character = player.Character
    local humanoid = character:WaitForChild("Humanoid")
    local TEST_DESTINATION = Vector3.new(228.9, 17.8, 292.5)
    local waypoints
    local nextWaypointIndex
    local reachedConnection
    local blockedConnection
    local function followPath(destination)
    -- 计算路径
    local success, errorMessage = pcall(function()
    path:ComputeAsync(character.PrimaryPart.Position, destination)
    end)
    if success and path.Status == Enum.PathStatus.Success then
    -- 获取路径路径点
    waypoints = path:GetWaypoints()
    -- 检测路径是否被阻塞
    blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
    -- 检查障碍物是否在路径下方
    if blockedWaypointIndex >= nextWaypointIndex then
    -- 在路径重计算后停止检测路径阻塞
    blockedConnection:Disconnect()
    -- 调用函数重新计算新路径
    followPath(destination)
    end
    end)
    -- 检测当前移动到下一个路径点完成时
    if not reachedConnection then
    reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
    if reached and nextWaypointIndex < #waypoints then
    -- 增加路径索引并移至下一个路径点
    nextWaypointIndex += 1
    -- 如果路径标签是“使用船”,请使用船;否则移动到下一个路径点
    if waypoints[nextWaypointIndex].Label == "UseBoat" then
    useBoat()
    else
    humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
    end
    else
    reachedConnection:Disconnect()
    blockedConnection:Disconnect()
    end
    end)
    end
    -- 初始移动到第二个路径点(第一个路径点是路径开始;跳过它)
    nextWaypointIndex = 2
    humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
    else
    warn("Path not computed!", errorMessage)
    end
    end
    function useBoat()
    local boat = Workspace.BoatModel
    humanoid.Seated:Connect(function()
    -- 如果代理坐下,开始船只移动
    if humanoid.Sit then
    task.wait(1)
    boat.CylindricalConstraint.Velocity = 5
    end
    -- 检测岛与约束位置之间的关系
    local boatPositionConnection
    boatPositionConnection = RunService.PostSimulation:Connect(function()
    -- 在岛屿旁停止船只
    if boat.CylindricalConstraint.CurrentPosition >= 94 then
    boatPositionConnection:Disconnect()
    boat.CylindricalConstraint.Velocity = 0
    task.wait(1)
    -- 卸下代理并继续前往目的地
    humanoid.Sit = false
    humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
    end
    end)
    end)
    end
    followPath(TEST_DESTINATION)

传输兼容性

体验中的实例流是一个强大的功能,可以动态加载和卸载 3D 内容,随着玩家角色在世界上移动。当他们探索 3D 空间时,新的子集流向他们的设备,现有的一些子集可能会流出。

考虑以下最佳实践,用于在启用流式体验时使用 PathfindingService

  • 传播可以阻止或解锁给定的路径,当角色沿着它移动时。例如,当角色通过森林时,树可能会在他们前面的某个地方流入,并阻碍路径。为了使路径抓取与流媒体无缝工作,强烈建议您使用 处理阻塞路径 技术并在必要时重新计算路径。

  • 在路径寻找中的一个常见方法是使用现有对象的坐标进行 计算,例如将路径目的地设置为世界上现有 宝箱模型 的位置。这种方法与服务器端 Scripts 完全兼容,因为服务器始终拥有世界的全景,但 LocalScriptsModuleScripts 运行在客户端的可能会失败,如果它们尝试计算不在输入中流式传输的对象的路径。

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