Penentuan Jalur adalah proses memindahkan karakter atau objek (agen) sepanjang jalur logis di sekitar rintangan untuk mencapai tujuan, secara opsional menghindari bahan berbahaya atau wilayah yang ditentukan.
Visualisasi navigasi
Untuk membantu dalam penataan dan debugging penentuan jalur, Studio dapat merender mesh navigasi dan label modifier. Untuk mengaktifkannya, aktifkan Mesh Navigasi dan Penentu Jalur modifiers dari widget Opsi Visualisasi di sudut kanan atas jendela tampilan 3D.

Dengan Mesh navigasi diaktifkan, area berwarna menunjukkan di mana karakter bisa berjalan atau berenang. Panah kecil menunjukkan area yang akan ditempuh karakter dengan melompat.

Implementasi
Meskipun penentuan jalur dapat diterapkan dengan berbagai cara melalui PathfindingService dan metodenya yang terkait seperti CreatePath(), bagian ini menggunakan skrip penentuan jalur berikut untuk karakter pemain.
Untuk menguji sambil membaca:
- PENTINGDi Explorer, pilih wadah StarterPlayer. Kemudian, di jendela Properties, atur baik DevComputerMovementMode dan DevTouchMovementMode ke Scriptable.


Salin kode berikut ke dalam LocalScript dalam StarterCharacterScripts, atau dapatkan paket ini dan letakkan di dalam 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-- Hitung jarak dari humanoid ke bidang tujuanlocal dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance-- Hitung komponen kecepatan humanoid yang menuju bidanglocal velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)-- Hitung ambang batas dari bidang tujuan berdasarkan kecepatan humanoidlocal threshold = math.max(1.0, VELOCITY_MULTIPLIER * velocity)-- Anggap waypoint tercapai jika kurang dari ambang batas di depan bidangreached = 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]-- Bangun bidang tujuan dari waypoint berikutnya menuju yang saat inicurrentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position-- Atur normal tegak lurus terhadap bidang Y saat tidak mendakiif 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-- Panggil fungsi khusus untuk membuat agen menggunakan perahuelseif 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()-- Hitung jalurpathfinderWorking = truewaitForGround()local success, errorMessage = pcall(function()path:ComputeAsync(character.PrimaryPart.Position, DESTINATION)end)if not success or path.Status ~= Enum.PathStatus.Success thenwarn("Jalur tidak dihitung!", errorMessage)returnend-- Dapatkan waypoint jalurwaypoints = path:GetWaypoints()-- Deteksi jika jalur terhalangblockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)-- Periksa jika rintangan berada lebih jauh di jalurif blockedWaypointIndex >= nextWaypointIndex then-- Hentikan deteksi rintangan jalur sampai jalur dihitung ulangblockedConnection:Disconnect()resetWaypointData()-- Panggil fungsi untuk menghitung ulang jalur barufollowPath()endend)findStartingPoint(waypoints)moveToNextWaypoint()endfollowPath()Edit variabel DESTINATION (
LINE 5) ke tujuan Vector3 dalam dunia 3D yang dapat dicapai oleh karakter pemain.Lanjutkan melalui bagian berikut untuk belajar tentang perhitungan jalur dan gerakan karakter.
Pembuatan jalur
Penentuan jalur dimulai melalui PathfindingService dan metode CreatePath() (
| Kunci | Deskripsi | Tipe | Default |
|---|---|---|---|
| AgentRadius | Radius agen, dalam stud. Berguna untuk menentukan pemisahan minimum dari rintangan. | integer | 2 |
| AgentHeight | Tinggi agen, dalam stud. Ruang kosong yang lebih kecil dari nilai ini, seperti ruang di bawah tangga, akan ditandai sebagai tidak dapat dilalui. | integer | 5 |
| AgentCanJump | Menentukan apakah melompat selama penentuan jalur diizinkan. | boolean | true |
| AgentCanClimb | Menentukan apakah mendaki TrussParts selama penentuan jalur diizinkan. Jalur yang bisa didaki memiliki Label bernama Climb dan biaya untuk jalur yang bisa didaki adalah 1 secara default. | boolean | false |
| WaypointSpacing | Jarak antara waypoint antara dalam jalur. Jika diatur ke math.huge, tidak akan ada waypoint antara. | number | 4 |
| Costs | Tabel bahan atau PathfindingModifiers yang ditentukan dan biaya mereka untuk dilalui. Berguna untuk membuat agen lebih suka bahan/wilayah tertentu dibandingkan yang lain. Lihat modifiers untuk rincian. | table | nil |
Perhitungan jalur
Setelah Anda membuat jalur yang valid dengan CreatePath(), jalur tersebut harus dihitung dengan memanggil Path:ComputeAsync() dengan Vector3 untuk titik awal dan tujuan (

Setelah Path dihitung, ia akan berisi serangkaian waypoints yang melacak jalur dari awal hingga akhir. Titik-titik ini dapat dikumpulkan dengan metode Path:GetWaypoints() (

Gerakan jalur
Setiap PathWaypoint terdiri dari Position (Vector3) dan Action (PathWaypointAction). Untuk memindahkan karakter yang memiliki Humanoid, seperti karakter Roblox yang biasa, cara terbaik adalah memanggil Humanoid:Move() dari waypoint ke waypoint dan menggunakan callback isCurrentWaypointReached() skrip (
Jalur yang terhalang
Banyak dunia Roblox bersifat dinamis; bagian mungkin bergerak atau jatuh, dan lantai mungkin runtuh. Ini dapat memblokir jalur yang dihitung dan mencegah karakter mencapai tujuannya. Untuk mengatasinya, Anda dapat menghubungkan acara Path.Blocked dan menghitung ulang jalur di sekitar apa pun yang menghalangnya (
Modifiers Penentuan Jalur
Secara default, Path:ComputeAsync() mengembalikan jalur terpendek antara titik awal dan tujuan, dengan pengecualian bahwa itu berusaha menghindari melompat. Ini terlihat tidak alami dalam beberapa situasi; misalnya, jalur mungkin melewati rawa daripada menghindarinya hanya karena jalur melalui air secara geometris lebih pendek.

Untuk lebih mengoptimalkan penentuan jalur, Anda dapat menerapkan modifiers penentuan jalur untuk menghitung jalur yang lebih cerdas di berbagai bahan, di sekitar wilayah yang ditentukan, atau untuk mengabaikan rintangan.
Biaya Bahan
Saat bekerja dengan bahan Terrain dan BasePart, Anda dapat menyertakan tabel Costs dalam CreatePath() untuk membuat bahan tertentu lebih mudah dilalui daripada yang lain. Semua bahan memiliki biaya default 1 dan bahan mana pun dapat didefinisikan sebagai tidak dapat dilalui dengan mengatur nilainya ke math.huge.
Kunci dalam tabel Costs harus nama string yang mewakili nama Enum.Material, misalnya Water untuk Enum.Material.Water atau CrackedLava untuk 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}})
Mengonfigurasi Wilayah
Dalam beberapa kasus, preferensi bahan tidak cukup. Misalnya, Anda mungkin ingin karakter menghindari wilayah tertentu, terlepas dari bahan di bawah kaki. Ini dapat dicapai dengan menambahkan objek PathfindingModifier ke bagian.
Buat sebuah bagian Anchored di sekitar wilayah tersebut dan atur propertinya CanCollide menjadi false.

Masukkan instance PathfindingModifier ke bagian tersebut, cari propertinya Label, dan berikan nama yang berarti seperti DangerZone.

Sertakan kunci yang cocok DangerZone dan nilai numerik yang terkait dalam tabel Costs dari CreatePath(). Modifier dapat didefinisikan sebagai tidak dapat dilalui dengan mengatur nilainya ke 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}})
Mengabaikan Rintangan
Dalam beberapa kasus, berguna untuk mencari jalan melalui rintangan solid seolah-olah mereka tidak ada. Ini memungkinkan Anda menghitung jalur melalui pemblokir fisik tertentu, daripada perhitungan gagal sepenuhnya.
Buat sebuah bagian Anchored di sekitar objek dan atur propertinya CanCollide menjadi false.

Masukkan instance PathfindingModifier ke bagian tersebut dan aktifkan propertinya PassThrough.

Sekarang, saat jalur dihitung dari NPC zombie ke karakter pemain, jalur tersebut memperluas di luar pintu dan Anda dapat memaksa zombie untuk melintasinya. Bahkan jika zombie tidak dapat membuka pintu, ia bereaksi seolah-olah "mendengar" karakter di balik pintu.

Tautan Penentuan Jalur
Terkadang perlu menemukan jalur di ruang yang tidak dapat dilalui secara normal, seperti melintasi jurang, dan melakukan tindakan khusus untuk mencapai waypoint berikutnya. Ini dapat dicapai melalui objek PathfindingLink.
Menggunakan contoh dari atas, Anda dapat membuat agen menggunakan perahu.

Untuk membuat PathfindingLink menggunakan contoh ini:
- OPSIONALAktifkan Tautan Penentuan Jalur dari widget Opsi Visualisasi di sudut kanan atas jendela tampilan 3D. Ini membantu dengan visualisasi dan debugging saat menerapkan tautan penentuan jalur.
Buat dua Attachments, satu di kursi perahu dan satu dekat titik pendaratan perahu.

Buat objek PathfindingLink dalam workspace, lalu tetapkan propertinya Attachment0 dan Attachment1 ke lampiran awal dan akhir masing-masing.


Berikan nama yang berarti seperti UseBoat pada propertinya Label. Nama ini digunakan sebagai tanda dalam skrip penentuan jalur untuk memicu tindakan khusus saat agen mencapai titik tautan awal.

Sertakan tabel Costs dalam CreatePath() yang berisi kunci Water dan kunci khusus yang cocok dengan nama properti Label. Tetapkan nilai kunci khusus lebih rendah daripada biaya 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}})Dalam fungsi moveToNextWaypoint() (
LINES 93–114), pemeriksaan khusus untuk nama modifier Label dapat digunakan untuk mengambil tindakan yang berbeda dari Humanoid:Move(); dalam hal ini, Anda mungkin memanggil fungsi untuk menempatkan agen di dalam perahu, memindahkan perahu melintasi air, mengeluarkan agen di titik pendaratan perahu, dan kemudian melanjutkan perjalanan agen ke tujuan akhir.
Kompatibilitas Streaming
Dalam-game streaming instance adalah fitur kuat yang secara dinamis memuat dan mengeluarkan konten 3D saat karakter pemain bergerak di sekitar dunia. Ketika mereka menjelajahi ruang 3D, subset baru dari ruang tersebut akan di-stream ke perangkat mereka dan beberapa subset yang ada mungkin di-stream keluar.
Pertimbangkan praktik terbaik berikut untuk menggunakan PathfindingService dalam permainan yang mendukung streaming:
Streaming dapat memblokir atau membuka blokir jalur tertentu saat karakter bergerak di sepanjangnya. Misalnya, saat seorang karakter berlari melalui hutan, sebuah pohon mungkin di-stream di depan mereka dan menghalangi jalur. Untuk membuat penentuan jalur bekerja dengan lancar dengan streaming, sangat disarankan untuk menggunakan teknik menangani jalur yang terhalang dan menghitung ulang jalur saat diperlukan.
Pendekatan umum dalam penentuan jalur adalah menggunakan koordinat objek yang ada untuk perhitungan, seperti mengatur tujuan jalur ke posisi model TreasureChest yang ada di dunia. Pendekatan ini sepenuhnya kompatibel dengan Scripts sisi server karena server memiliki pandangan penuh dunia setiap saat, tetapi LocalScripts dan ModuleScripts yang dijalankan di klien mungkin gagal jika mereka berusaha menghitung jalur ke objek yang tidak di-stream.
Untuk mengatasi masalah ini, pertimbangkan untuk mengatur tujuan ke posisi BasePart dalam model persisten. Model persistent dimuat segera setelah pemain bergabung dan mereka tidak pernah di-stream keluar, jadi skrip sisi klien dapat terhubung ke acara PersistentLoaded dan dengan aman mengakses model untuk membuat waypoint setelah acara dipicu.
Batasan dan faktor kegagalan
Mesin penentuan jalur memiliki batasan tertentu untuk memastikan pemrosesan yang efisien dan kinerja optimal. Selain itu, perhitungan penentuan jalur perhitungan dapat gagal karena berbagai alasan yang diuraikan di bawah ini.