角色路径探索

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

寻路 是移动角色沿着逻辑路径到达目的地的过程,避免障碍和 (可选) 危险材料或定义区域。

导航视图

为了在路径找寻布局和调试中提供帮助,Studio 可以渲染一个导航网格和 调整 标签。 要启用它们,请从 3D 视窗的右上角的 视图选项 小组中的 导航网格 和 2>调整2> 标签切换。

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

启用了 导航网格 ,彩色区域显示角色可能走路或游泳的地方,而非颜色区域是否被封锁。小箭头指示区域,角色可以通过跳跃尝试达到,假设您设置了 AgentCanJumptrue,当1> 创建路径1> 时。

Navigation mesh showing in Studio

使用 路径修改器 时,文本标签会指示具体材料和区域,当使用 路径修改器 时考虑到。

Navigation labels showing on navigation mesh

已知的限制

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

垂直放置限制

路径寻找计算仅考虑垂直边界内的部分:

  • 低限制 — 零件的底部 Y 坐标小于 -65,536 格是无视的。
  • 上限 — 超出 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决定是否允许在寻路时跳跃。booleantrue
AgentCanClimb决定是否允许在寻路时攀爬 TrussPartsbooleanfalse
WaypointSpacing在路径中间的间隔。如果设置为 math.huge ,将不会有间隔。4
Costs材料表或定义 PathfindingModifiers 和它们的价格为 traversal。 有助于使代理优先使用某些材料/区域 over others。 请参阅modifiers 获取详细信息。nil
本地脚本

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

注意,当您设置 TrussPartsAgentCanClimb 时,当您创建路径时,并且没有什么阻止路径从桁架上攀爬,当前可以攀爬的路径有 true

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. 编辑线 11 到一个 Vector3 目的地,玩家角色可以达到。
  3. 阅读以下部分了解路径计算和角色移动。
本地脚本 - 角色路径找寻

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
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() 之后,它必须通过调用 Class.Path:ComputeAsync() 与一个 8> Datatype.Vector3 为开始点和目的地。

本地脚本 - 角色路径找寻

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
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 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 ) 和一个 行动 ( 2> Class.Humanoid:MoveTo2> ) 组

本地脚本 - 角色路径找寻

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
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 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

要进一步优化路径寻找,您可以实现 路径寻找修改 来计算更智能的路径通过各种材料定义区域和1>障碍1>。

设置材料成本

使用 TerrainBasePart 材料时,您可以包含一个 Costs 表在 1>Class.PathfindingService:CreatePath()|CreatePath() 内,以使某些材料比其他材料更可行走。所有材料的

Costs 表中,键名应该是代表 Enum.Material 名称的字符串名,例如 Water 对于 1> Enum.Material.Water1> .

本地脚本 - 角色路径找寻

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

与区域工作

在某些情况下,材质优先级 不足。例如,您可能希望角色避免 定义区域 ,无论是否使用脚下的材料。这可以通过添加一个PathfindingModifier 对象来实现。

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

    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()

    本地脚本 - 角色路径找寻

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

忽略障碍

在某些情况下,通过“作为 if 不存在”的方式通过固体障碍物是有用的。这允许您通过特定物理方块计算路径,而不是通过计算失败。

  1. 在对象周围创建一个 Anchored 零件,设置其 CanCollide 属性为

    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. 将有意义的名称,例如 使用船只 分配到其 Label 属性。 此名称在路径寻找脚本中作为标志在路径寻找脚本中使用,当代理到达起始链接时触发自定义操作。

    Label property specified for PathfindingLink
  5. Costs 中包含一个包含 CreatePath() 键和一个与 Water 属性名称匹配的自定义键。 将自定义键设置为低于 2>Water2> 。

    本地脚本 - 角色路径找寻

    local PathfindingService = game:GetService("PathfindingService")
    local Players = game:GetService("Players")
    local RunService = game:GetService("RunService")
    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 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 的最佳实践:

  • 流媒体可以阻塞或解锁给定路径,因为角色沿着它移动。例如,当角色通过森林时,树可能会从某个地方流入前方并阻塞道路。为了使路径找到顺畅工作,它非常建议您使用处理阻塞道路技术,并在必要时重新计算道路。

  • 在寻路方法中,使用现有对象的坐标进行<a href=\"#计算\">计算</a>,例如将路径目的地设置为现有 <a href=\"#TreasureChest|TreasureChest_in_the_world\">Treasure

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