การค้นหาเส้นทาง คือกระบวนการเคลื่อนที่ของตัวละครหรือวัตถุ (ตัวแทน) ตามเส้นทางที่มีเหตุผลรอบๆ อุปสรรคเพื่อไปยังจุดหมาย โดยเลือกที่จะหลีกเลี่ยงวัตถุที่เป็นอันตรายหรือพื้นที่ที่กำหนด
การแสดงภาพการนำทาง
เพื่อช่วยในการจัดเรียงและการดีบักการค้นหาเส้นทาง สตูดิโอสามารถแสดงเมชการนำทางและ ตัวแก้ไข ได้ เพื่อเปิดใช้งานให้เปิดสวิตช์ Navigation mesh และ Pathfinding modifiers จากวิจิตรมุมมอง Visualization Options ที่มุมขวาบนของมุมมอง 3D

เมื่อมีการเปิดใช้งาน เมชการนำทาง พื้นที่ที่มีสีจะบ่งบอกว่าสามารถเดินหรือลงน้ำได้ ตัวลูกศรเล็กๆ แสดงพื้นที่ที่ตัวละครจะพยายามไปให้ถึงโดยการกระโดด

การดำเนินการ
แม้ว่าการค้นหาเส้นทางสามารถดำเนินการได้หลายวิธีโดยใช้ PathfindingService และวิธีการที่เกี่ยวข้อง เช่น CreatePath(), ส่วนนี้จะใช้สคริปการค้นหาเส้นทางสำหรับตัวละครของผู้เล่น
เพื่อลองทดสอบในขณะที่อ่าน:
- สำคัญใน Explorer ให้เลือกคอนเทนเนอร์ StarterPlayer จากนั้นในหน้าต่าง Properties ตั้งค่า DevComputerMovementMode และ DevTouchMovementMode เป็น Scriptable


คัดลอกโค้ดต่อไปนี้ไปยัง LocalScript ภายใน StarterCharacterScripts หรือ รับแพ็กเกจนี้ และวางมันไว้ใน 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-- คำนวณระยะทางจากฮิวมานอยด์ไปยังระนาบจุดหมายlocal dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance-- คำนวณส่วนประกอบของความเร็วของฮิวมานอยด์ที่มุ่งไปที่ระนาบlocal velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)-- คำนวณเกณฑ์จากระนาบจุดหมายตามความเร็วของฮิวมานอยด์local threshold = math.max(1.0, VELOCITY_MULTIPLIER * velocity)-- ถือว่าถึงจุดหมายหากน้อยกว่าเกณฑ์ในด้านหน้าreached = 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]-- สร้างระนาบจุดหมายจากจุดต่อไปไปยังปัจจุบันcurrentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position-- ตั้งนอร์มอลตั้งฉากกับระนาบ Y เมื่อไม่ปีนขึ้นif 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-- เรียกใช้ฟังก์ชันที่คุณกำหนดเองเพื่อทำให้ตัวแทนใช้เรือelseif 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()-- คำนวณเส้นทางpathfinderWorking = truewaitForGround()local success, errorMessage = pcall(function()path:ComputeAsync(character.PrimaryPart.Position, DESTINATION)end)if not success or path.Status ~= Enum.PathStatus.Success thenwarn("ไม่สามารถคำนวณเส้นทางได้!", errorMessage)returnend-- รับทางที่เป็นไปได้waypoints = path:GetWaypoints()-- ตรวจสอบว่าทางติดขัดblockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)-- ตรวจสอบว่ามีอุปสรรคอยู่ไกลลงไปในเส้นทางif blockedWaypointIndex >= nextWaypointIndex then-- หยุดการตรวจจับการติดขัดจนกว่าจะมีการคำนวณใหม่blockedConnection:Disconnect()resetWaypointData()-- เรียกฟังก์ชันเพื่อคำนวณเส้นทางใหม่followPath()endend)findStartingPoint(waypoints)moveToNextWaypoint()endfollowPath()ดำเนินการตามส่วนถัดไปเพื่อเรียนรู้เกี่ยวกับการคำนวณเส้นทางและการเคลื่อนที่ของตัวละคร
การสร้างเส้นทาง
การค้นหาเส้นทางเริ่มต้นผ่าน PathfindingService และวิธี CreatePath() (
| คีย์ | คำบรรยาย | ประเภท | ค่าเริ่มต้น |
|---|---|---|---|
| AgentRadius | รัศมีของตัวแทน เป็นหน่วยสตัด สำหรับการกำหนดการแยกตัวจากอุปสรรคอย่างน้อยที่สุด | integer | 2 |
| AgentHeight | ความสูงของตัวแทน เป็นหน่วยสตัด พื้นที่ว่างที่มีขนาดเล็กกว่านี้ เช่น พื้นที่ใต้บันได จะถูกทำเครื่องหมายว่าไม่สามารถผ่านได้ | integer | 5 |
| AgentCanJump | กำหนดว่าสามารถกระโดดในระหว่างการค้นหาเส้นทางได้หรือไม่ | boolean | true |
| AgentCanClimb | กำหนดว่าสามารถปีน TrussParts ในระหว่างการค้นหาเส้นทางได้หรือไม่ เส้นทางที่สามารถปีนได้มี Label ชื่อว่า Climb และ ค่าใช้จ่าย สำหรับเส้นทางที่สามารถปีนได้คือ 1 โดยเริ่มต้น | boolean | false |
| WaypointSpacing | ระยะห่างระหว่างจุดทางกึ่งกลางในเส้นทาง หากตั้งค่าเป็น math.huge จะไม่มีจุดทางกึ่งกลาง | number | 4 |
| Costs | ตารางของวัสดุหรือ PathfindingModifiers ที่กำหนดและค่าใช้จ่ายในการเดินทางผ่าน มีประโยชน์สำหรับทำให้ตัวแทนชอบวัสดุ/พื้นที่บางประเภทมากกว่าชนิดอื่น ดูรายละเอียดที่ ตัวแก้ไข | table | nil |
การคำนวณเส้นทาง
หลังจากที่คุณสร้างเส้นทางที่ถูกต้องสาหรับ CreatePath() แล้ว มันต้องถูก คำนวณ โดยเรียก Path:ComputeAsync() ด้วย Vector3 ทั้งสำหรับจุดเริ่มต้นและจุดหมาย (

เมื่อ Path ถูกคำนวณ มันจะมีชุดของ จุดทาง ที่แสดงเส้นทางจากจุดเริ่มต้นไปยังจุดสิ้นสุด จุดเหล่านี้สามารถรวบรวมได้ด้วยวิธี Path:GetWaypoints() (

การเคลื่อนที่ตามเส้นทาง
แต่ละ PathWaypoint ประกอบด้วยทั้ง Position (Vector3) และ Action (PathWaypointAction). เพื่อเคลื่อนที่ตัวละครที่มี Humanoid, เช่น ตัวละคร Roblox ปกติ วิธีที่ดีที่สุดคือเรียกใช้ Humanoid:Move() จากจุดทางไปยังจุดทางและใช้การกลับคืนของสคริปต์ isCurrentWaypointReached() (
เส้นทางที่ถูกบล็อก
โลกใน Roblox หลายแห่งเป็นแบบพลศาสตร์; บางส่วนอาจเคลื่อนที่หรือหล่นและพื้นอาจถล่ม สิ่งนี้สามารถบล็อกเส้นทางที่เผามาและป้องกันไม่ให้ตัวละครถึงจุดหมายของตน เพื่อจัดการกับสิ่งนี้ คุณสามารถเชื่อมต่อกับเหตุการณ์ Path.Blocked และคำนวณเส้นทางใหม่รอบๆ สิ่งที่บล็อกมัน (
ตัวแก้ไขการค้นหาเส้นทาง
ตามค่าเริ่มต้น, Path:ComputeAsync() จะส่งคืน เส้นทางที่สั้นที่สุด ระหว่างจุดเริ่มต้นและจุดหมาย โดยยกเว้นว่าพยายามหลีกเลี่ยงการกระโดด สิ่งนี้ดูไม่เป็นธรรมชาติเสมอไปในบางสถานการณ์; เช่นเส้นทางอาจผ่านน้ำโคลนแทนที่จะไปโดยรอบเพียงเพราะเส้นทางผ่านน้ำจะสั้นมาก

เพื่อเพิ่มประสิทธิภาพการค้นหาเส้นทางให้ดียิ่งขึ้น คุณสามารถนำเข้าตัวแก้ไขการค้นหาเส้นทางเพื่อคำนวณเส้นทางที่ชาญฉลาดกว่าผ่านวัสดุที่แตกต่างกัน วัสดุ, รอบๆ พื้นที่, หรือ ละเว้นอุปสรรค.
ค่าใช้จ่ายวัสดุ
เมื่อทำงานกับวัสดุของ Terrain และ BasePart, คุณสามารถรวมตาราง Costs ภายใน CreatePath() เพื่อทำให้วัสดุบางประเภทสามารถเดินทางได้ง่ายกว่าวัสดุอื่นๆ วัสดุทั้งหมดมีค่าใช้จ่ายเริ่มต้นที่ 1 และวัสดุใดๆ สามารถกำหนดเป็นไม่สามารถผ่านได้โดยการตั้งค่าค่าของมันเป็น math.huge.
คีย์ในตาราง Costs ควรเป็นชื่อ string ที่แสดงถึงชื่อของ Enum.Material, เช่น Water สำหรับ Enum.Material.Water หรือ CrackedLava สำหรับ 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}})
กำหนดค่าพื้นที่
ในบางกรณี, ความชอบวัสดุ ไม่เพียงพอ ตัวอย่างเช่น คุณอาจต้องการให้ตัวละครหลีกเลี่ยง พื้นที่ที่กำหนด, ไม่ว่าจะเป็นวัสดุใดๆ ที่อยู่ล่าง สิ่งนี้สามารถทำได้โดยการเพิ่มออบเจ็กต์ PathfindingModifier ลงในส่วน
สร้างส่วน Anchored รอบพื้นที่และตั้งค่าคุณสมบัติ CanCollide เป็น false.

แทรกอินสแตนซ์ PathfindingModifier ลงในส่วน ตรวจสอบคุณสมบัติ Label และกำหนดชื่อที่มีความหมายเช่น DangerZone.

รวมคีย์ DangerZone และค่าตัวเลขที่สัมพันธ์กับไว้ภายในตาราง Costs ของ CreatePath(). ตัวแก้ไขสามารถกำหนดเป็นไม่สามารถผ่านได้โดยการตั้งค่าค่าของมันเป็น 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}})
ละเว้นอุปสรรค
ในบางกรณี เป็นเรื่องที่มีประโยชน์ในการค้นหาเส้นทางผ่านอุปสรรคที่เป็นของแข็งราวกับว่ามันไม่อยู่ สิ่งนี้ทำให้คุณสามารถคำนวณเส้นทางผ่านตัวบล็อกทางกายภาพเฉพาะต่างๆ แทนที่จะทำให้การคำนวณล้มเหลว
สร้างส่วน Anchored รอบวัตถุและตั้งค่าคุณสมบัติ CanCollide เป็น false.

แทรกอินสแตนซ์ PathfindingModifier ลงในส่วนและเปิดใช้งานคุณสมบัติ PassThrough.

ตอนนี้เมื่อคำนวณเส้นทางจาก NPC ซอมบี้ไปยังตัวละครผู้เล่น เส้นทางจะแผ่ไปไกลกว่าประตู และคุณสามารถสั่งให้ซอมบี้เคลื่อนที่ผ่านไปได้ แม้ว่าซอมบี้จะไม่สามารถเปิดประตูได้ แต่ก็สามารถตอบสนองเป็นเหมือนกับว่า "ได้ยิน" ตัวละครที่อยู่หลังประตู

ลิงก์การค้นหาเส้นทาง
บางครั้งจำเป็นต้องค้นหาเส้นทางข้ามพื้นที่ที่ไม่สามารถผ่านได้ตามปกติ เช่น ข้ามหุบเหว และดำเนินการเพื่อให้ถึงจุดทางถัดไป สิ่งนี้สามารถทำได้ผ่านวัตถุ PathfindingLink
จากตัวอย่างข้างต้น คุณสามารถทำให้ตัวแทนใช้เรือ

ในการสร้าง PathfindingLink โดยใช้ตัวอย่างนี้:
- ทางเลือกเปิดใช้งาน Pathfinding links จากวิจิตรมุมมอง Visualization Options ที่มุมขวาบนของมุมมอง 3D สิ่งนี้ช่วยในการแสดงภาพและดีบักเมื่อดำเนินการเชื่อมโยงการค้นหาเส้นทาง
สร้าง Attachments สองตัว หนึ่งที่นั่งของเรือและอีกตัวใกล้จุดลงของเรือ

สร้างวัตถุ PathfindingLink ในพื้นที่ทำงาน จากนั้นกำหนดคุณสมบัติ Attachment0 และ Attachment1 ให้กับการยึดเริ่มต้นและจุดสิ้นสุดตามลำดับ


กำหนดชื่อที่มีความหมาย เช่น ใช้เรือ ให้กับคุณสมบัติ Label. ชื่อนี้จะถูกใช้เป็นสัญลักษณ์ในสคริปการค้นหาเส้นทางเพื่อเริ่มการกระทำที่กำหนดเองเมื่อเอเจนท์ถึงจุดเริ่มต้นของลิงก์

รวมตาราง Costs ภายใน CreatePath() ที่ประกอบด้วยทั้งคีย์ Water และคีย์ที่กำหนดเองที่ตรงกับชื่อคุณสมบัติ Label. มอบค่าต่ำกว่า กว่าค่าใช้จ่ายของน้ำ
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}})ในฟังก์ชัน moveToNextWaypoint() (
บรรทัด 93–114), การตรวจสอบที่กำหนดเองสำหรับชื่อโมดิฟายเออร์ Label สามารถใช้เพื่อดำเนินการที่แตกต่างจาก Humanoid:Move(); ในกรณีนี้ คุณอาจเรียกใช้ฟังก์ชันเพื่อทำให้ตัวแทนนั่งในเรือ นำเรือไปข้ามน้ำ ไล่ที่นั่งออกที่จุดลงจอดของเรือ และจากนั้นก็ดำเนินการเส้นทางของตัวแทนไปยังจุดหมายสุดท้าย
ความเข้ากันได้กับการสตรีม
การสตรีม การใช้งานในเกม เป็นฟีเจอร์ที่มีประสิทธิภาพที่โหลดและปลดระวางเนื้อหา 3D โดยอัตโนมัติเมื่อคล้ายกับการเคลื่อนไหวของตัวละครผู้เล่น เมื่อพวกเขาสำรวจพื้นที่ 3D เซตใหม่ของพื้นที่จะไหลเข้ามายังอุปกรณ์ของพวกเขาและส่วนที่มีอยู่บางส่วนอาจถูกสตรีมออก
พิจารณาแนวทางปฏิบัติที่ดีที่สุดต่อไปนี้เมื่อใช้ PathfindingService ในเกมที่เปิดอยู่รอบ:
สตรีมสามารถบล็อกหรือปลดบล็อกเส้นทางใดเส้นทางหนึ่งเมื่อมีการเคลื่อนที่ของตัวละครไปตามเส้นทางนั้น ตัวอย่างเช่น ขณะที่ตัวละครวิ่งผ่านป่า อาจมีต้นไม้ที่สตรีมเข้ามาตรงหน้าพวกเขาและขวางทาง เพื่อให้การค้นหาเส้นทางสามารถทำงานร่วมกันกับสตรีมได้อย่างราบรื่น แนะนำอย่างมากให้คุณใช้ เทคนิค การจัดการเส้นทางที่ติดขัด และคำนวณเส้นทางใหม่เมื่อจำเป็น
วิธีการทั่วไปในการค้นหาเส้นทางคือการใช้พิกัดของวัตถุที่มีอยู่ในการ คำนวณ, เช่นการตั้งจุดหมายเส้นทางไปยังตำแหน่งของโมเดล TreasureChest ที่มีอยู่ในโลก วิธีการนี้เข้ากันได้อย่างเต็มที่กับ Scripts ฝั่งเซิร์ฟเวอร์ เนื่องจากเซิร์ฟเวอร์มีมุมมองครบถ้วนของโลกตลอดเวลา แต่ LocalScripts และ ModuleScripts ที่ทำงานในฝั่งไคลเอนต์อาจล้มเหลวหากพยายามคำนวณเส้นทางไปยังวัตถุที่ไม่ได้สตรีมเข้า
เพื่อแก้ไขปัญหานี้ พิจารณาในการตั้งค่าจุดหมายไปยังตำแหน่งของ BasePart ภายในโมเดลที่ คงอยู่. โมเดลที่อย่างยั่งยืนโหลดทันทีหลังจากที่ผู้เล่นเข้าร่วมและจะไม่สตรีมออกดังนั้นสคริปต์ฝั่งไคลเอนต์สามารถเชื่อมต่อกับเหตุการณ์ PersistentLoaded และเข้าถึงโมเดลเพื่อสร้างทางได้อย่างปลอดภัยหลังจากเหตุการณ์เกิดขึ้น
ข้อจำกัดและปัจจัยที่ทำให้ล้มเหลว
ระบบการค้นหาเส้นทางรวมถึงข้อจำกัดเฉพาะเพื่อให้แน่ใจว่าการประมวลผลมีประสิทธิภาพและประสิทธิภาพการทำงานที่เหมาะสม นอกจากนี้ การคำนวณการค้นหาเส้นทาง สามารถล้มเหลว ด้วยเหตุผลต่างๆ ตามที่อธิบายไว้ด้านล่าง