경로 탐색은 캐릭터나 객체(요원)가 장애물을 피하며 목적지에 도달하기 위해 논리적 경로를 따라 이동하는 과정입니다. 이 과정에서 위험한 물질이나 정의된 구역을 피할 수도 있습니다.
탐색 시각화
경로 탐색 레이아웃 및 디버깅을 도와주기 위해 스튜디오는 탐색 메쉬 및 수정기 레이블을 렌더링할 수 있습니다. 이를 활성화하려면 3D 뷰포트 오른쪽 상단의 시각화 옵션 위젯에서 탐색 메쉬 및 경로 탐색 수정기를 켜세요.

탐색 메쉬가 활성화되면 색이 있는 영역은 캐릭터가 걷거나 수영할 수 있는 곳을 보여줍니다. 작은 화살표는 캐릭터가 점프하여 도달하려고 시도하는 영역을 나타냅니다.

구현
경로 탐색은 PathfindingService와 그에 관련된 메소드들, 예를 들어 CreatePath()를 통해 여러 가지 방법으로 구현할 수 있지만, 이 섹션에서는 플레이어 캐릭터를 위한 다음 경로 탐색 스크립트를 사용합니다.
읽으면서 테스트하기 위해:
- 중요탐색기에서 StarterPlayer 컨테이너를 선택합니다. 그런 다음 속성 창에서 DevComputerMovementMode와 DevTouchMovementMode를 모두 Scriptable로 설정합니다.


다음 코드를 LocalScript에 복사하여 StarterCharacterScripts 안에 붙여넣거나 이 패키지를 가져와 StarterCharacterScripts에 드롭하세요.
PlayerPathFollow (StarterCharacterScripts의 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}})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-- Compute the distance from humanoid to destination planelocal dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance-- Compute the component of the humanoid velocity that is towards the planelocal velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)-- Compute the threshold from the destination plane based on humanoid velocitylocal threshold = math.max(1.0, VELOCITY_MULTIPLIER * velocity)-- Consider waypoint reached if less then threshold in front of the planereached = 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]-- Build destination plane from next waypoint towards current onecurrentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position-- Set normal perpendicular to Y plane when not climbing upif 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-- Call your own customized function to make agent use the boatelseif 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()-- Compute the pathpathfinderWorking = 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-- Get the path waypointswaypoints = path:GetWaypoints()-- Detect if path becomes blockedblockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)-- Check if the obstacle is further down the pathif blockedWaypointIndex >= nextWaypointIndex then-- Stop detecting path blockage until path is re-computedblockedConnection:Disconnect()resetWaypointData()-- Call function to re-compute new pathfollowPath()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)을 모두 포함합니다. 일반적인 Roblox 캐릭터와 같은 Humanoid를 포함하는 캐릭터를 이동하기 위한 가장 좋은 방법은 웨이포인트에서 웨이포인트로 Humanoid:Move()를 호출하고, 스크립트의 isCurrentWaypointReached() 콜백 (
차단된 경로
많은 Roblox 세계는 동적입니다. 일부 부품은 이동하거나 떨어질 수 있고 바닥은 무너질 수 있습니다. 이로 인해 계산된 경로가 차단되어 캐릭터가 목적지에 도달하지 못할 수 있습니다. 이를 처리하기 위해서는 Path.Blocked 이벤트에 연결하고 장애물 주변으로 경로를 재계산할 수 있습니다 (
경로 탐색 수정기
기본적으로 Path:ComputeAsync()는 시작점과 목적지 사이의 최단 경로를 반환합니다. 단, 점프를 피하도록 시도합니다. 이는 일부 상황에서는 부자연스럽게 보일 수 있습니다. 예를 들어, 경로가 수로를 우회하는 대신 수로를 통과하도록 설정되어 있는 이유는 수로를 통과하는 경로가 기하학적으로 더 짧기 때문입니다.

경로 탐색을 더욱 최적화하기 위해 다양한 재료, 정의된 구역을 가로지르거나 장애물을 무시하는 경로 탐색 수정기를 구현할 수 있습니다.
재료 비용
Terrain 및 BasePart 재료 작업 시, CreatePath() 내에 Costs 테이블을 포함하여 특정 재료가 다른 재료보다 더 쉽게 통과할 수 있도록 만들 수 있습니다. 모든 재료의 기본 비용은 1이며, 어떤 재료든지 값을 math.huge로 설정하여 비가로잡을 수 없는 것으로 정의할 수 있습니다.
Costs 테이블의 키는 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과 같은 의미 있는 이름을 할당합니다.

CreatePath()의 Costs 테이블에 일치하는 DangerZone 키와 관련된 숫자 값을 포함합니다. 수정기를 비가로잡을 수 없는 것으로 정의하려면 값을 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를 생성하려면:
보트의 좌석 및 보트 착륙 지점 근처에 Attachments 두 개를 생성합니다.

UseBoat와 같은 의미 있는 이름을 Label 속성에 할당합니다. 이 이름은 에이전트가 시작 링크 지점에 도달할 때 사용자 정의 작업을 트리거하기 위한 플래그로 사용됩니다.

CreatePath() 내에 Water 키와 Label 속성 이름과 일치하는 사용자 정의 키를 포함하는 Costs 테이블을 포함합니다. 사용자 정의 키의 값은 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를 사용할 때의 최선의 관행은 다음과 같습니다:
스트리밍은 캐릭터가 이동할 때 주어진 경로를 차단하거나 차단 해제할 수 있습니다. 예를 들어, 캐릭터가 숲을 달리는 동안, 나무가 앞쪽 어딘가에 스트리밍되어 경로를 obstruct할 수 있습니다. 스트리밍과 원활하게 작동하는 경로 탐색을 위해, 차단된 경로 처리 기술을 사용하는 것이 권장되며 필요할 때 경로를 재계산해야 합니다.
경로 탐색에서 일반적인 접근 방법은 기존 객체의 좌표를 사용하여 계산하는 것입니다. 예를 들어, 경로 목적지를 세계에 존재하는 TreasureChest 모델의 위치로 설정하는 것입니다. 이 접근 방식은 서버에서 항상 세계를 완전히 볼 수 있으므로 서버 측 Scripts와 완벽하게 호환되지만, 클라이언트에서 실행되는 LocalScripts 및 ModuleScripts가 스트리밍되지 않은 객체에 대해 경로를 계산하려고 하면 실패할 수 있습니다.
이 문제를 해결하기 위해, 지속적인 모델 내의 BasePart 위치로 목적지를 설정하는 것을 고려해 보세요. 지속적인 모델은 플레이어가 참여한 후 곧바로 로드되며 절대 스트리밍 아웃되지 않으므로, 클라이언트 측 스크립트가 PersistentLoaded 이벤트에 연결하고 이벤트가 발생한 후 웨이포인트 생성을 위한 모델에 안전하게 접근할 수 있습니다.
제한 사항 및 실패 요인
경로 탐색 엔진에는 효율적인 프로세싱과 최적의 성능을 보장하기 위한 특정 제한 사항이 있습니다. 또한 경로 탐색 계산은 다음과 같은 여러 이유로 실패할 수 있습니다.

