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ướng và Bộ 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.

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.

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:
- QUAN TRỌNGTrong Explorer, chọn container StarterPlayer. Sau đó, trong cửa sổ Thuộc tính, đặt cả DevComputerMovementMode và DevTouchMovementMode thành Scriptable.


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.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-- Tính khoảng cách từ nhân vật đến mặt phẳng điểm đếnlocal 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ẳnglocal 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ậtlocal 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ẳngreached = 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]-- Tạo mặt phẳng điểm đến từ waypoint tiếp theo về waypoint hiện tạicurrentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position-- Đặt phương vuông góc với mặt phẳng Y khi không leo lênif 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-- Gọi hàm tùy chỉnh của bạn để làm cho đại diện sử dụng thuyềnelseif 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()-- Tính toán đường đipathfinderWorking = truewaitForGround()local success, errorMessage = pcall(function()path:ComputeAsync(character.PrimaryPart.Position, DESTINATION)end)if not success or path.Status ~= Enum.PathStatus.Success thenwarn("Đường đi không được tính toán!", errorMessage)returnend-- Lấy các waypointwaypoints = path:GetWaypoints()-- Phát hiện nếu đường đi bị chặnblockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)-- Kiểm tra nếu chướng ngại vật ở xa hơn xuống đường điif 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ạiblockedConnection:Disconnect()resetWaypointData()-- Gọi hàm để tính toán lại đường đi mớifollowPath()endend)findStartingPoint(waypoints)moveToNextWaypoint()endfollowPath()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.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() (
| Khóa | Mô tả | Kiểu | Mặc định |
|---|---|---|---|
| AgentRadius | Bá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ên | 2 |
| AgentHeight | Chiề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ên | 5 |
| AgentCanJump | Quyết định liệu nhảy trong khi tìm đường có được phép hay không. | boolean | true |
| AgentCanClimb | Quyế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 Label là Climb và chi phí cho một lộ trình có thể leo lên là 1 theo mặc định. | boolean | false |
| WaypointSpacing | Khoả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 |
| Costs | Bả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ảng | nil |
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 (

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

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 (
Đườ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 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.

Để 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 Terrain và BasePart, 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.01local VELOCITY_MULTIPLIER = 0.0625local 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.
Tạo một phần Anchored quanh khu vực và đặt thuộc tính CanCollide thành false.

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.

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.01local VELOCITY_MULTIPLIER = 0.0625local 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.
Tạo một phần Anchored quanh đối tượng và đặt thuộc tính CanCollide của nó thành false.

Chèn một instance PathfindingModifier vào phần và bật thuộc tính PassThrough.

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.

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.

Để tạo một PathfindingLink sử dụng ví dụ này:
- TÙY CHỌNBậ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.
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.

Tạo một đối tượng PathfindingLink trong workspace, sau đó gán thuộc tính Attachment0 và Attachment1 của nó đến các attachment bắt đầu và kết thúc tương ứng.


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.

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.01local VELOCITY_MULTIPLIER = 0.0625local path = PathfindingService:CreatePath({AgentCanClimb = true,Costs = {UseBoat = 2, Water = 20}})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 LocalScripts và ModuleScripts 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.