Tìm đường

*Nội dung này được dịch bằng AI (Beta) và có thể có lỗi. Để xem trang này bằng tiếng Anh, hãy nhấp vào đây.

Tìm đường là quá trình di chuyển một nhân vật hoặc đối tượng (đại diện) theo một lộ trình hợp lý vòng qua các chướng ngại vật để đạt đến một điểm đến, có thể tránh các vật liệu nguy hiểm hoặc các khu vực đã định nghĩa.

Hình ảnh điều hướng

Để hỗ trợ bố trí tìm đường và gỡ lỗi, Studio có thể hiển thị một lưới điều hướng và các nhãn bộ sửa đổi. Để kích hoạt chúng, hãy bật Lưới điều hướngBộ sửa đổi tìm đường từ widget Tùy chọn hình ảnh ở góc trên bên phải của cửa sổ 3D.

Góc gần của cửa sổ 3D với nút Tùy chọn hình ảnh được chỉ định ở góc trên bên phải.

Khi Lưới điều hướng được bật, các khu vực có màu cho thấy nơi một nhân vật có thể đi bộ hoặc bơi. Các mũi tên nhỏ chỉ ra những khu vực mà nhân vật sẽ cố gắng đến bằng cách nhảy.

Lưới điều hướng được hiển thị trong Studio

Triển khai

Mặc dù tìm đường có thể được triển khai theo nhiều cách khác nhau thông qua PathfindingService và các phương thức liên quan như CreatePath(), phần này sử dụng đoạn script tìm đường sau cho nhân vật của người chơi.

Để thử nghiệm trong khi đọc:

  1. QUAN TRỌNG
    Trong Explorer, chọn container StarterPlayer. Sau đó, trong cửa sổ Thuộc tính, đặt cả DevComputerMovementModeDevTouchMovementMode thành Scriptable.

  2. Sao chép mã sau vào một LocalScript trong StarterCharacterScripts, hoặc lấy gói này và thả nó vào 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
    -- Tính khoảng cách từ nhân vật đến mặt phẳng điểm đến
    local dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance
    -- Tính thành phần vận tốc của nhân vật hướng về mặt phẳng
    local velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)
    -- Tính ngưỡng từ mặt phẳng điểm đến dựa trên vận tốc nhân vật
    local threshold = math.max(1.0, VELOCITY_MULTIPLIER * velocity)
    -- Xem như đã đến waypoint nếu dưới ngưỡng ở phía trước mặt phẳng
    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]
    -- Tạo mặt phẳng điểm đến từ waypoint tiếp theo về waypoint hiện tại
    currentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position
    -- Đặt phương vuông góc với mặt phẳng Y khi không leo lên
    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
    -- Gọi hàm tùy chỉnh của bạn để làm cho đại diện sử dụng thuyền
    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()
    -- Tính toán đường đi
    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("Đường đi không được tính toán!", errorMessage)
    return
    end
    -- Lấy các waypoint
    waypoints = path:GetWaypoints()
    -- Phát hiện nếu đường đi bị chặn
    blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
    -- Kiểm tra nếu chướng ngại vật ở xa hơn xuống đường đi
    if blockedWaypointIndex >= nextWaypointIndex then
    -- Dừng việc phát hiện chặn đường cho đến khi đường đi được tính toán lại
    blockedConnection:Disconnect()
    resetWaypointData()
    -- Gọi hàm để tính toán lại đường đi mới
    followPath()
    end
    end)
    findStartingPoint(waypoints)
    moveToNextWaypoint()
    end
    followPath()
  3. Chỉnh sửa biến DESTINATION (

    DÒNG 5
    ) thành một điểm đến kiểu Vector3 trong thế giới 3D mà nhân vật người chơi có thể đến được.

  4. Tiến hành qua các phần sau để tìm hiểu về việc tính toán đường đi và di chuyển nhân vật.

Tạo đường đi

Tìm đường được khởi động thông qua PathfindingService và phương thức CreatePath() (

CÁC DÒNG 9–14
). Phương thức này chấp nhận một bảng tham số tùy chọn giúp tinh chỉnh cách nhân vật (đại diện) di chuyển dọc theo đường đi.

KhóaMô tảKiểuMặc định
AgentRadiusBán kính của đại diện, tính bằng studs. Hữu ích để xác định khoảng cách tối thiểu từ các chướng ngại vật.số nguyên2
AgentHeightChiều cao của đại diện, tính bằng studs. Không gian trống nhỏ hơn giá trị này, như không gian dưới cầu thang, sẽ được đánh dấu là không thể đi qua.số nguyên5
AgentCanJumpQuyết định liệu nhảy trong khi tìm đường có được phép hay không.booleantrue
AgentCanClimbQuyết định liệu việc leo lên TrussParts trong khi tìm đường có được phép hay không. Một lộ trình có thể leo lên có nhãn LabelClimbchi phí cho một lộ trình có thể leo lên là 1 theo mặc định.booleanfalse
WaypointSpacingKhoảng cách giữa các waypoint trung gian trong đường đi. Nếu đặt thành math.huge, sẽ không có các waypoint trung gian.số4
CostsBảng các vật liệu hoặc PathfindingModifiers đã định nghĩa và chi phí của chúng để di chuyển qua. Hữu ích để làm cho đại diện ưa thích một số vật liệu/khu vực hơn những vật liệu/khu vực khác. Xem bộ sửa đổi để biết chi tiết.bảngnil

Tính toán đường đi

Sau khi bạn đã tạo một đường đi hợp lệ với CreatePath(), nó phải được tính toán bằng cách gọi Path:ComputeAsync() với một Vector3 cho cả điểm bắt đầu và điểm đến (

CÁC DÒNG 133–139
).

Điểm bắt đầu/kết thúc của đường đi được đánh dấu trên hai cây cầu

Khi Path được tính toán, nó sẽ chứa một loạt các waypoint đánh dấu đường đi từ bắt đầu đến kết thúc. Những điểm này có thể được thu thập bằng phương thức Path:GetWaypoints() (

DÒNG 142
). Mảng trả về được sắp xếp theo thứ tự các waypoint từ điểm bắt đầu đường đi đến điểm kết thúc đường đi.

Các waypoint được chỉ ra trên đường đi đã được tính toán
Các waypoint được chỉ ra trên đường đi đã được tính toán

Di chuyển theo đường đi

Mỗi PathWaypoint bao gồm cả một Position (Vector3) và Action (PathWaypointAction). Để di chuyển một nhân vật có chứa Humanoid, như một nhân vật Roblox điển hình, cách tốt nhất là gọi Humanoid:Move() từ waypoint này sang waypoint khác và sử dụng callback isCurrentWaypointReached() của script (

CÁC DÒNG 32–56
) để phát hiện khi nào nhân vật đến từng waypoint.

Đường đi bị chặn

Nhiều thế giới Roblox là động; các phần có thể di chuyển hoặc rơi và sàn có thể sụp đổ. Điều này có thể chặn một đường đi đã được tính toán và ngăn nhân vật đến được điểm đến. Để xử lý việc này, bạn có thể kết nối sự kiện Path.Blocked và tính toán lại đường đi vòng qua bất cứ điều gì cản trở (

CÁC DÒNG 145–154
).

Các bộ sửa đổi tìm đường

Theo mặc định, Path:ComputeAsync() trả về đường đi ngắn nhất giữa điểm bắt đầu và điểm đến, ngoại trừ việc nó cố gắng tránh nhảy. Điều này trông không tự nhiên trong một số tình huống; ví dụ, một đường có thể đi qua nước đầm lầy thay vì vòng qua nó chỉ vì đường đi qua nước là ngắn hơn về mặt hình học.

Hai đường đi được chỉ ra với đường ngắn hơn không nhất thiết phải hợp lý hơn

Để tối ưu hóa tìm đường thêm nữa, bạn có thể triển khai các bộ sửa đổi tìm đường để tính toán các đường đi thông minh hơn qua các vật liệu, vòng qua các khu vực đã định nghĩa, hoặc để bỏ qua các chướng ngại vật.

Chi phí vật liệu

Khi làm việc với các vật liệu TerrainBasePart, bạn có thể bao gồm một bảng Costs trong CreatePath() để làm cho một số vật liệu dễ đi qua hơn những vật liệu khác. Tất cả các vật liệu có chi phí mặc định là 1 và bất kỳ vật liệu nào cũng có thể được định nghĩa là không thể đi qua bằng cách đặt giá trị của nó thành math.huge.

Các khóa trong bảng Costs nên là các tên chuỗi đại diện cho tên của Enum.Material, ví dụ Water cho Enum.Material.Water hoặc CrackedLava cho 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
}
})

Cấu hình khu vực

Trong một số trường hợp, sở thích vật liệu không đủ. Ví dụ, bạn có thể muốn những nhân vật tránh một khu vực đã định nghĩa, bất kể vật liệu phía dưới. Điều này có thể đạt được bằng cách thêm một đối tượng PathfindingModifier vào một phần.

  1. Tạo một phần Anchored quanh khu vực và đặt thuộc tính CanCollide thành false.

    Phần được neo định nghĩa một khu vực để áp dụng bộ sửa đổi tìm đường.
  2. Chèn một instance PathfindingModifier vào phần, tìm thuộc tính Label của nó, và gán một tên có ý nghĩa như DangerZone.

    Instance PathfindingModifier với thuộc tính Label được đặt thành DangerZone.
  3. Bao gồm một khóa DangerZone phù hợp và giá trị số liên quan trong bảng Costs của CreatePath(). Một bộ sửa đổi có thể được định nghĩa là không khả dụng bằng cách đặt giá trị của nó thành 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
    }
    })

Bỏ qua các chướng ngại vật

Trong một số trường hợp, việc tìm đường qua các chướng ngại vật rắn như thể chúng không tồn tại là hữu ích. Điều này cho phép bạn tính toán một đường đi qua các vật cản vật lý cụ thể, thay vì tính toán bị thất bại hoàn toàn.

  1. Tạo một phần Anchored quanh đối tượng và đặt thuộc tính CanCollide của nó thành false.

    Phần được neo định nghĩa một khu vực để áp dụng bộ sửa đổi tìm đường.
  2. Chèn một instance PathfindingModifier vào phần và bật thuộc tính PassThrough.

    Instance PathfindingModifier với thuộc tính PassThrough được bật.

    Bây giờ, khi một đường được tính toán từ NPC zombie đến nhân vật người chơi, đường đi mở rộng qua cánh cửa và bạn có thể yêu cầu zombie đi qua nó. Ngay cả khi zombie không thể mở cửa, nó phản ứng như thể nó "nghe" nhân vật ở phía sau cánh cửa.

    Đường đi của NPC zombie đi qua cánh cửa đã từng cản trở.

Liên kết tìm đường

Đôi khi cần phải tìm một đường đi qua một không gian không thể được đi qua bình thường, chẳng hạn như qua một khe sâu, và thực hiện một hành động tùy chỉnh để đến waypoint tiếp theo. Điều này có thể đạt được thông qua đối tượng PathfindingLink.

Sử dụng ví dụ từ trên, bạn có thể làm cho đại diện sử dụng một chiếc thuyền.

PathfindingLink cho thấy cách một đại diện có thể sử dụng một chiếc thuyền.

Để tạo một PathfindingLink sử dụng ví dụ này:

  1. TÙY CHỌN
    Bật Liên kết tìm đường từ widget Tùy chọn hình ảnh ở góc trên bên phải của cửa sổ 3D. Điều này giúp trong việc hình ảnh hóa và gỡ lỗi khi triển khai liên kết tìm đường.

  2. Tạo hai Attachments, một trên ghế của thuyền và một gần điểm hạ của thuyền.

    Các Attachment được tạo cho điểm bắt đầu và kết thúc của liên kết tìm đường.
  3. Tạo một đối tượng PathfindingLink trong workspace, sau đó gán thuộc tính Attachment0Attachment1 của nó đến các attachment bắt đầu và kết thúc tương ứng.

    Các thuộc tính Attachment0/Attachment1 của một PathfindingLink. PathfindingLink được hình dung trong thế giới 3D.
  4. Gán một tên có ý nghĩa như UseBoat cho thuộc tính Label của nó. Tên này được sử dụng như một cờ trong script tìm đường để kích hoạt một hành động tùy chỉnh khi đại diện đến điểm liên kết bắt đầu.

    Thuộc tính Label được chỉ định cho PathfindingLink.
  5. Bao gồm một bảng Costs trong CreatePath() chứa cả một khóa Water và một khóa tùy chỉnh phù hợp với tên thuộc tính Label. Gán cho khóa tùy chỉnh một giá trị thấp hơn giá trị của 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. Trong hàm moveToNextWaypoint() (

    CÁC DÒNG 93–114
    ), có thể sử dụng một kiểm tra tùy chỉnh cho tên bộ sửa đổi Label để thực hiện một hành động khác không phải là Humanoid:Move(); trong trường hợp này, bạn có thể gọi một hàm để đặt đại diện vào chiếc thuyền, di chuyển chiếc thuyền qua nước, đưa đại diện ra khỏi chiếc thuyền tại điểm hạ của chiếc thuyền, và sau đó tiếp tục đường đi của đại diện đến điểm đến cuối cùng của nó.

Tương thích với Streaming

Tính năng streaming instance trong trò chơi là một tính năng mạnh mẽ cho phép tải và hủy tải nội dung 3D một cách động khi nhân vật của người chơi di chuyển xung quanh thế giới. Khi họ khám phá không gian 3D, một số phần mới của không gian được phát trực tuyến vào thiết bị của họ và một số phần hiện tại có thể bị hủy tải.

Hãy xem xét các thực hành tốt nhất sau đây khi sử dụng PathfindingService trong các trò chơi có hỗ trợ streaming:

  • Streaming có thể chặn hoặc mở khóa một đường nhất định trong khi một nhân vật di chuyển dọc theo nó. Ví dụ, trong khi một nhân vật chạy qua một khu rừng, một cái cây có thể phát trực tuyến vào đâu đó phía trước họ và chắn đường đi. Để làm cho việc tìm đường hoạt động liền mạch với streaming, bạn nên sử dụng kỹ thuật xử lý đường đi bị chặn và tính toán lại đường đi khi cần thiết.

  • Một cách tiếp cận phổ biến trong việc tìm đường là sử dụng tọa độ của các đối tượng hiện có cho việc tính toán, chẳng hạn như đặt đích của đường vào vị trí của một mô hình TreasureChest có sẵn trong thế giới. Cách tiếp cận này hoàn toàn tương thích với Scripts phía máy chủ vì máy chủ luôn có cái nhìn toàn bộ về thế giới, nhưng các LocalScriptsModuleScripts chạy ở phía client có thể thất bại nếu chúng cố gắng tính toán một đường đến một đối tượng chưa được phát trực tuyến.

    Để giải quyết vấn đề này, hãy xem xét đặt đích đến là vị trí của một BasePart trong một mô hình bền vững. Các mô hình bền vững tải ngay sau khi người chơi tham gia và chúng không bao giờ bị hủy tải, vì vậy một script phía client có thể kết nối với sự kiện PersistentLoaded và truy cập an toàn vào mô hình để tạo waypoints sau khi sự kiện này kích hoạt.

Các giới hạn và yếu tố thất bại

Động cơ tìm đường bao gồm các giới hạn cụ thể để đảm bảo quá trình xử lý hiệu quả và hiệu suất tối ưu. Hơn nữa, các tính toán đường đi có thể thất bại vì nhiều lý do như đã nêu dưới đây.

©2026 Roblox Corporation. Roblox, logo Roblox và Powering Imagination là các nhãn hiệu đã đăng ký và chưa đăng ký của chúng tôi tại Hoa Kỳ và các quốc gia khác.