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

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

การนำไปใช้
แม้ว่าการหาทางสามารถนำไปใช้ในหลายวิธีผ่าน 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()แก้ไขตัวแปร DESTINATION (
บรรทัด 5) เป็นจุดหมาย Vector3 ภายในโลก 3D ที่ตัวละครผู้เล่นสามารถเข้าถึงได้ดำเนินการผ่านส่วนถัดไปเพื่อเรียนรู้เกี่ยวกับการคำนวณเส้นทางและการเคลื่อนที่ของตัวละคร
การสร้างเส้นทาง
การหาทางเริ่มต้นผ่าน PathfindingService และวิธี CreatePath() (
| คีย์ | คำอธิบาย | ประเภท | ค่าเริ่มต้น |
|---|---|---|---|
| AgentRadius | รัศมีของตัวแทนในหน่วยสตัด ใช้สำหรับกำหนดการแยกขั้นต่ำจากอุปสรรค | จำนวนเต็ม | 2 |
| AgentHeight | ความสูงของตัวแทนในหน่วยสตัด พื้นที่ว่างที่มีขนาดเล็กกว่าค่านี้ เช่น พื้นที่ใต้บันได จะถูกทำเครื่องหมายว่าไม่สามารถข้ามได้ | จำนวนเต็ม | 5 |
| AgentCanJump | กำหนดว่าการกระโดดระหว่างการหาทางอนุญาตหรือไม่ | บูลีน | true |
| AgentCanClimb | กำหนดว่าการปีน TrussParts ระหว่างการหาทางอนุญาตหรือไม่ เส้นทางที่สามารถปีนได้มี Label ชื่อว่า Climb และ ค่าใช้จ่าย สำหรับเส้นทางที่สามารถปีนได้คือ 1 โดยค่าเริ่มต้น | บูลีน | false |
| WaypointSpacing | ระยะห่างระหว่างจุดหมายกลางในเส้นทาง หากตั้งค่าเป็น math.huge จะไม่มีจุดหมายกลาง | จำนวน | 4 |
| Costs | ตารางของวัสดุหรือ PathfindingModifiers ที่กำหนดและค่าใช้จ่ายสำหรับการข้าม ใช้สำหรับทำให้ตัวแทนชอบวัสดุ/พื้นที่บางอย่างมากกว่าที่อื่น ดู modifiers สำหรับรายละเอียด | ตาราง | 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 ควรเป็นชื่อ สตริง ที่แสดงถึงชื่อ Enum.Material เช่น Water สำหรับ Enum.Material.Water หรือ CrackedLava สำหรับ Enum.Material.CrackedLava
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
}
})กำหนดพื้นที่
ในบางกรณี ความชอบวัสดุ ไม่เพียงพอ ตัวอย่างเช่น คุณอาจต้องการให้ตัวละครหลีกเลี่ยง พื้นที่ที่กำหนด โดยไม่คำนึงถึงวัสดุที่อยู่ใต้เท้า สิ่งนี้สามารถทำได้โดยการเพิ่มวัตถุ 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 ให้กับการแนบจุดเริ่มต้นและจุดสิ้นสุดตามลำดับ


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

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