로블록스 Studio NPC가 멈출 때: Pathfinding 경로·이동 분리 점검법
NPC가 서 있기만 하면 흔히 PathfindingService부터 의심하지만, 실제로는 경로 계산 실패와 계산된 waypoint를 따라가지 못한 이동 실패가 같은 화면으로 보입니다. 먼저 ComputeAsync 결과와 waypoint 수를 기록하고, 그다음 통로 크기·점프 행동·MoveToFinished·Blocked·스트리밍을 한 단계씩 분리해야 원인을 좁힐 수 있습니다. 무작정 AgentRadius를 줄이거나 매 프레임 경로를 다시 계산하기 전에 확인할 기준을 정리했습니다.
2026년 10월 11일 Windows 데스크톱 브라우저에서 Roblox Creator Hub의 Pathfinding 안내, PathfindingService·Path·Humanoid API, instance streaming techniques 원문과 공개 Markdown 메타데이터를 서로 대조했습니다. 특히 “경로를 못 만들었다”와 “경로는 만들었지만 Humanoid가 waypoint에 도착하지 못했다”를 같은 오류로 다루지 않도록 진단 순서를 다시 구성했습니다. 실제 Roblox Studio에서 NPC 모델을 실행하거나 특정 Place의 통로·네트워크 소유권·물리를 측정하지는 않았습니다. 아래 코드는 원인 분리용 뼈대이며 공개 전 자신의 NPC와 맵에서 Server & Clients 테스트가 필요합니다.
ComputeAsync()를 pcall()로 감싸고 path.Status와 #path:GetWaypoints()를 먼저 출력하세요. 계산이 실패하면 내비게이션 메시·거리·Agent 매개변수를 보고, Success인데 멈추면 waypoint 행동·Humanoid 이동·물리 방해를 봅니다. 이동 중에만 멈추면 Path.Blocked, MoveToFinished와 스트리밍을 확인합니다.1. 경로 계산 실패와 이동 실패를 먼저 분리합니다
PathfindingService는 시작점과 목적지 사이의 논리적 경로를 계산합니다. 계산 결과가 성공해도 NPC의 Humanoid가 자동으로 움직이지는 않습니다. 반대로 NPC가 움직이지 않는다고 해서 계산 자체가 실패했다고 단정할 수도 없습니다. 같은 “제자리” 증상을 아래 네 상태로 분리하면 수정할 파일이 달라집니다.
| 관찰 상태 | 뜻 | 다음 확인 |
|---|---|---|
pcall 실패 | 계산 호출에서 오류 발생 | start·finish 값, 객체 생존, 오류 메시지 |
| Status가 Success 아님 | 엔진이 유효한 경로를 만들지 못함 | Navigation mesh, Agent 크기, 공식 제한 |
| Success + waypoint 있음, 첫 이동 없음 | 경로 전달 뒤 Humanoid·모델 쪽 문제 | RootPart 고정, Humanoid 상태, 이동 호출 |
| 몇 개 waypoint 뒤 중단 | 동적 장애물·점프·이동 timeout 가능성 | 현재 index, Blocked index, reached 값 |
진단 중에는 “경로 계산”, “waypoint 선택”, “실제 이동”을 서로 다른 함수와 로그 단계로 둡니다. 하나의 긴 함수에서 실패할 때마다 처음부터 재귀 호출하면 어느 단계에서 막혔는지 사라지고 재계산이 겹칠 수 있습니다.
2. 코드보다 먼저 Navigation mesh를 확인합니다
Creator Hub는 Studio 3D 뷰포트 오른쪽 위의 Visualization Options에서 Navigation mesh와 Pathfinding modifiers를 켜도록 안내합니다. 색이 표시된 영역은 에이전트가 걸을 수 있다고 판단한 공간이고 작은 화살표는 점프 시도를 나타냅니다.
- NPC의 현재 발 위치와 목적지 바닥이 모두 내비게이션 메시 위에 있는지 확인합니다.
- 두 영역 사이의 문·계단·다리에서 메시가 끊기는 지점을 찾습니다.
- 천장 아래, 문틀, 난간 사이가 NPC 높이와 폭을 수용하는지 실제 stud 크기로 잽니다.
- PathfindingModifier가 붙은 영역의 Label과
Costs키 철자가 같은지 확인합니다. math.huge비용이나 PassThrough가 의도하지 않은 영역에 적용되지 않았는지 봅니다.
메시가 끊긴 맵을 코드 재시도로 해결할 수는 없습니다. 반대로 메시가 이어져 있는데도 Status가 실패한다면 start·finish 위치와 Agent 매개변수, 거리 제한을 다음 단계에서 확인합니다. 메시 표시와 실제 NPC 충돌은 별도이므로, 경로가 보인다는 사실만으로 Humanoid 이동 성공까지 보장되지는 않습니다.
3. ComputeAsync 결과와 상태를 한 번에 기록합니다
CreatePath()는 경로 객체를 만들고, Path:ComputeAsync(start, finish)가 실제 계산을 수행합니다. 이 호출은 yield하며 오류가 날 수 있으므로 pcall()의 성공 여부와 Path.Status를 따로 기록합니다. Success를 확인하기 전에 GetWaypoints()를 정상 경로처럼 사용하지 마세요.
local PathfindingService = game:GetService("PathfindingService")
local function computePath(startPosition, destination)
local path = PathfindingService:CreatePath({
AgentRadius = 2,
AgentHeight = 5,
AgentCanJump = true,
AgentCanClimb = false,
WaypointSpacing = 4,
})
local ok, errorMessage = pcall(function()
path:ComputeAsync(startPosition, destination)
end)
if not ok then
warn("path-compute-error", errorMessage)
return nil, nil, "call-error"
end
if path.Status ~= Enum.PathStatus.Success then
warn("path-status", path.Status.Name)
return path, nil, path.Status.Name
end
local waypoints = path:GetWaypoints()
print("path-success", #waypoints)
return path, waypoints, nil
end
운영 로그에는 시작·목적지 좌표를 과도한 소수점 그대로 남기기보다 적절히 반올림한 값, Agent 매개변수, Status, waypoint 수, 계산에 걸린 시간을 함께 남기면 재현하기 쉽습니다. 단순히 “NPC failed”만 출력하면 거리 제한인지 문 폭인지 구분할 수 없습니다.
4. AgentRadius·AgentHeight를 NPC 외형과 맞춥니다
공식 기본값은 AgentRadius=2, AgentHeight=5, AgentCanJump=true, AgentCanClimb=false, WaypointSpacing=4입니다. 기본값은 모든 NPC의 실제 크기가 아니라 계산의 출발점입니다.
- AgentRadius: 장애물에서 확보해야 하는 최소 수평 간격에 영향을 줍니다. 너무 크면 통과 가능한 문도 막힌 것으로 계산하고, 너무 작으면 경로는 만들어져도 실제 몸이 문틀에 걸릴 수 있습니다.
- AgentHeight: 이보다 낮은 빈 공간을 통과 불가로 봅니다. 큰 NPC에 기본 높이를 그대로 쓰면 경로와 실제 충돌이 어긋날 수 있습니다.
- AgentCanJump: 목적지에 점프가 필수인데 false이면 계산이 실패할 수 있습니다. true여도 코드가 Jump waypoint 행동을 실행하지 않으면 이동 단계에서 멈출 수 있습니다.
- AgentCanClimb: TrussPart 경로가 필요하면 활성화 여부와 Climb label 비용을 함께 확인합니다.
- Costs: 숫자가 클수록 해당 재질·Label을 덜 선호하고
math.huge는 통과 불가로 만듭니다.
원인을 찾을 때 Radius와 Height를 모두 1로 줄이는 방식은 좋은 실험이 아닙니다. 계산만 성공하고 실제 NPC가 충돌하는 거짓 양성이 생깁니다. NPC 충돌 상자의 폭·높이, 가장 좁은 문과 낮은 천장을 재고 한 변수씩 바꾼 뒤 Navigation mesh 변화와 실제 이동을 함께 기록하세요.
5. 3,000 studs보다 짧아도 노드 예산에서 실패할 수 있습니다
공식 Pathfinding 문서는 시작점과 끝점의 직선거리가 3,000 studs를 넘을 수 없고, 복잡한 미로나 넓은 세계에서는 그 전에 20,000 node 계산 예산을 소진할 수 있다고 명시합니다. waypoint 후보의 바닥 Y가 -65,536보다 작거나 65,536보다 큰 영역도 계산 대상에서 제외됩니다.
3,000 studs 이하라는 이유만으로 정상 경로를 기대하면 안 됩니다. 복잡한 장애물이 많은 맵은 중간 목적지나 구역 그래프로 나누고, 현재 구역의 짧은 로컬 경로만 PathfindingService로 계산하는 방법을 검토합니다. 장거리 목적지를 매 프레임 다시 계산하면 실패율뿐 아니라 서버 계산 비용도 커집니다.
시작점이 떨어지는 중이거나 바닥에서 비정상적으로 떠 있으면 적절한 시작 지점을 잡기 어려울 수 있습니다. 공식 예제도 Humanoid가 지면에 닿기를 기다린 뒤 계산합니다. NPC 생성 직후라면 FloorMaterial, RootPart 위치와 바닥 Raycast를 확인하고 안정된 상태에서 첫 계산을 시작하세요.
6. waypoint 위치만 보지 말고 Action도 처리합니다
GetWaypoints()는 시작점부터 끝점 순서의 배열을 반환하며 각 PathWaypoint에는 Position, Action, Label이 있습니다. Position만 순회하면 평지에서는 우연히 작동해도 점프 구간에서 멈출 수 있습니다.
Action == Enum.PathWaypointAction.Jump이면 해당 구간 진입 전에 Humanoid를 Jumping 상태로 전환합니다.- PathfindingLink를 사용한다면 waypoint Label을 보고 사다리·배·문 열기처럼 게임 고유 행동을 별도 함수로 처리합니다.
- 첫 waypoint가 현재 위치와 사실상 같으면 다음 waypoint부터 이동하되, 무조건 2번부터라는 가정 대신 거리 기준으로 검사합니다.
- 현재 index와 목표 Position을 표시하는 임시 Part나 Attachment는 Studio 진단에만 쓰고 공개 버전에서 제거합니다.
WaypointSpacing을 크게 만들어 점 수를 줄이면 굽은 복도에서 중간 제어점이 부족해지고, 너무 작게 하면 이동 호출과 로그가 늘어납니다. 기본값 4에서 출발해 실제 통로 곡률과 NPC 속도로 측정하세요.
7. MoveToFinished의 false는 경로 계산 실패가 아닙니다
NPC Humanoid를 MoveTo()로 waypoint까지 보내는 구현이라면 MoveToFinished(reached)의 false를 별도로 다뤄야 합니다. Humanoid API는 목표에 약 8초 안에 도달하지 못하면 이동이 timeout되고 false가 발생한다고 설명합니다. 이 값은 “처음부터 경로가 없었다”가 아니라 “정해진 이동 목표에 시간 안에 도달하지 못했다”는 신호입니다.
false가 나오면 즉시 같은 경로를 무한 반복하기보다 아래 상태를 기록합니다.
- 현재 waypoint index와 남은 거리
- RootPart가 Anchored인지, NPC 일부가 맵에 고정·용접됐는지
- WalkSpeed가 0이거나 Humanoid가 Dead·PlatformStanding 같은 이동 불가 상태인지
- 다른 NPC·문·움직이는 Part가 실제 충돌로 길을 막았는지
- Jump waypoint 직전에 점프 상태가 실행됐는지
timeout을 피하려고 짧은 간격으로 MoveTo를 다시 호출하는 공식 API 예제도 있지만, 이는 물리적으로 걸린 NPC를 해결하지 않습니다. 진행 거리 없이 timeout만 계속 갱신하면 멈춤을 숨길 뿐입니다. 마지막 위치와 현재 위치의 차이를 측정해 실제 전진이 없을 때만 재계산·복구하도록 설계하세요.
8. Blocked가 울려도 지나온 길이면 재계산하지 않습니다
동적 문, 떨어지는 Part, 붕괴하는 바닥은 계산 뒤 경로를 막을 수 있습니다. 공식 문서는 Path.Blocked에 연결하고 장애물이 현재 진행 지점보다 앞에 있을 때 경로를 다시 계산하는 패턴을 제시합니다.
local nextWaypointIndex = 1
local needsRecompute = false
local blockedConnection = path.Blocked:Connect(function(blockedIndex)
if blockedIndex >= nextWaypointIndex then
needsRecompute = true
print("path-blocked-ahead", blockedIndex, nextWaypointIndex)
end
end)
뒤에서 잔해가 떨어져 이미 지나온 waypoint가 막혀도 Blocked가 발생할 수 있습니다. index 비교 없이 재계산하면 정상적으로 앞으로 가던 NPC가 되돌아가거나 계산 폭주가 생깁니다. 새 경로 계산 전 기존 Blocked 연결을 끊고, 한 NPC에 활성 경로·활성 연결·재계산 작업이 각각 하나만 남도록 generation 번호나 취소 토큰을 둡니다.
Blocked만 믿는 것도 충분하지 않습니다. 실제 이동이 timeout됐지만 Blocked가 오지 않는 경우가 있으므로 MoveToFinished(false), 일정 시간 동안의 이동 거리, Blocked를 서로 다른 복구 신호로 기록합니다.
9. 스트리밍에서는 클라이언트와 서버가 다른 지도를 볼 수 있습니다
공식 streaming techniques 문서는 클라이언트의 PathfindingService가 현재 stream in된 geometry만 본다고 명시합니다. 서버에는 존재하는 먼 벽이 클라이언트에 아직 없으면 클라이언트 경로가 그 벽을 통과하는 것처럼 계산될 수 있습니다.
- 세계 전체 장애물을 반영해야 하는 NPC AI는 서버 계산을 우선 검토합니다.
- LocalScript가 먼 목적지 Model을 직접 참조하면 그 모델이 아직 stream in되지 않아 계산 전에 nil이 될 수 있습니다.
- 클라이언트 경로가 필요하다면 목적지를 Persistent 모델의 BasePart로 두고
Workspace.PersistentLoaded이후 접근하는 공식 패턴을 검토합니다. - 이동 중 새 나무나 문이 stream in되면 기존 경로가 막힐 수 있으므로 Blocked 이후 재계산이 필요합니다.
서버에서 성공하고 한 클라이언트에서만 이상하다면 AgentRadius를 먼저 바꾸지 마세요. 같은 시점의 Server Explorer와 Client Explorer, Streaming 패널을 비교해 장애물과 목적지가 양쪽에 실제로 존재하는지 확인합니다. 스트리밍 자체의 객체 존재·선로딩 진단은 스트리밍 모델 누락 점검법을 함께 참고하세요.
10. 계산·이동·복구 사유가 보이는 진단용 코드
아래 코드는 ServerScriptService에서 NPC 한 개를 진단하기 위한 최소 뼈대입니다. 목적지 검증, 여러 NPC 스케줄링, 네트워크 소유권, 문 열기, 공격 상태, 취소 토큰까지 포함한 완성 AI가 아닙니다. 출력되는 단계와 index를 확인한 뒤 자신의 상태 머신에 옮기세요.
local PathfindingService = game:GetService("PathfindingService")
local npc = workspace:WaitForChild("TestNPC")
local humanoid = npc:WaitForChild("Humanoid")
local root = npc:WaitForChild("HumanoidRootPart")
local destination = workspace:WaitForChild("PathDestination")
local function followOnce()
local path = PathfindingService:CreatePath({
AgentRadius = 2,
AgentHeight = 5,
AgentCanJump = true,
WaypointSpacing = 4,
})
local ok, errorMessage = pcall(function()
path:ComputeAsync(root.Position, destination.Position)
end)
if not ok then
return false, "compute-error: " .. tostring(errorMessage)
end
if path.Status ~= Enum.PathStatus.Success then
return false, "status: " .. path.Status.Name
end
local waypoints = path:GetWaypoints()
if #waypoints < 2 then
return false, "too-few-waypoints"
end
local nextIndex = 2
local blockedAhead = false
local blockedConnection = path.Blocked:Connect(function(blockedIndex)
if blockedIndex >= nextIndex then
blockedAhead = true
end
end)
for index = 2, #waypoints do
nextIndex = index
local waypoint = waypoints[index]
if waypoint.Action == Enum.PathWaypointAction.Jump then
humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
humanoid:MoveTo(waypoint.Position)
local reached = humanoid.MoveToFinished:Wait()
if blockedAhead then
blockedConnection:Disconnect()
return false, "blocked-ahead: " .. index
end
if not reached then
blockedConnection:Disconnect()
return false, "move-timeout: " .. index
end
end
blockedConnection:Disconnect()
return true, "arrived"
end
local arrived, reason = followOnce()
print("path-result", arrived, reason)
MoveToFinished:Wait() 중 Blocked가 발생해도 현재 MoveTo가 끝날 때까지 기다리는 단순 진단 코드입니다. 운영에서는 Blocked가 앞쪽에서 감지되면 현재 이동 작업을 취소하고 debounce 후 한 번만 재계산하도록 상태 머신을 구성합니다. 목적지가 계속 움직이는 플레이어라면 거리·시간 임계값을 정해 목적지 변화가 충분할 때만 새 경로를 만드세요.
11. 증상별 첫 확인 지점
| 증상 | 가능성 높은 구간 | 첫 확인 |
|---|---|---|
| Status가 Success가 아님 | 경로 계산 | Navigation mesh, 거리, AgentRadius·Height·Jump |
| Success인데 waypoint 1에서 정지 | 이동 시작 | RootPart Anchored, Humanoid 상태, 첫 목표 index |
| 계단 앞에서 멈춤 | 점프·높이 | waypoint Action, AgentCanJump, 실제 계단 충돌 |
| 문이 닫힌 뒤 계속 벽으로 감 | 동적 장애물 | Blocked 연결, blockedIndex와 nextIndex 비교 |
| 몇 초 뒤 MoveToFinished false | 실제 이동 | 8초 timeout 전 진행 거리와 물리 방해 |
| 큰 NPC만 경로 실패 | 에이전트 크기 | 문·천장 실측과 Radius·Height |
| 가까이 가면 갑자기 경로 변경 | 스트리밍 | 클라이언트에 새 장애물 stream in 여부 |
| NPC 수가 늘면 끊김 | 재계산 폭주 | 초당 계산 횟수, 중복 이벤트 연결, 이동 목적지 흔들림 |
| 경로는 벽을 통과해 표시 | 클라이언트 세계 누락 | 서버/클라이언트 Explorer와 streaming 범위 비교 |
12. 작은 직선 맵에서 시작해 장애물을 하나씩 추가합니다
- 새 테스트 Place에 넓은 평면, 표준 크기 NPC, 고정 목적지 Part만 둡니다.
- Navigation mesh가 이어지고 Status, waypoint 수, 최종 reached가 정상인지 기록합니다.
- 폭을 측정한 문 하나를 추가해 AgentRadius 경계 전후를 비교합니다.
- 낮은 천장을 추가해 AgentHeight 변화가 메시와 Status에 미치는 영향을 봅니다.
- 점프가 필요한 턱을 추가하고 Jump action 처리 전후를 비교합니다.
- 이동 중 문을 닫아 Blocked index가 현재 index 앞쪽에서 발생하는지 확인합니다.
- 지나온 길 뒤에 장애물을 떨어뜨려 불필요한 재계산이 없는지 확인합니다.
- StreamingEnabled 상태에서 서버 계산과 클라이언트 계산의 waypoint를 각각 기록합니다.
- 그다음 실제 맵에 같은 로그를 옮겨 어느 단계부터 결과가 달라지는지 찾습니다.
실제 맵부터 수십 개 변수를 동시에 바꾸면 성공해도 이유를 알 수 없습니다. 테스트 Place에서 정상 기준을 만든 뒤 문 폭, 점프, 장애물, 스트리밍을 하나씩 추가해야 문제를 재현하고 수정 후 회귀 테스트도 할 수 있습니다.
13. 운영 로그는 이유별로 묶고 재계산에 상한을 둡니다
NPC마다 매 waypoint를 무기한 출력하면 필요한 오류가 묻힙니다. 한 번의 이동 시도에 correlation ID를 만들고 다음 이벤트만 구조적으로 남깁니다.
- compute-start: NPC ID, 시작·목적지 구역, Agent 매개변수
- compute-result: 성공 여부, Status, waypoint 수, 소요 시간
- move-failed: index, Action, 남은 거리, Humanoid 상태
- blocked: blockedIndex, currentIndex, 앞·뒤 판정
- recompute: 사유, 최근 일정 시간의 횟수, cooldown
- arrived: 전체 소요 시간과 재계산 횟수
재계산은 오류 처리이면서 비용입니다. 짧은 시간에 허용할 최대 횟수와 cooldown을 두고, 상한을 넘으면 NPC를 안전한 스폰으로 되돌리거나 이동을 취소합니다. 재귀적으로 즉시 다시 호출하는 대신 스케줄러가 다음 시도를 한 번만 예약하게 하면 이벤트 연결과 Path 객체가 누적되는 문제도 줄일 수 있습니다.
14. 공개 전 점검표
- ComputeAsync를 pcall로 감싸고 호출 오류와 Path.Status를 분리했습니다.
- Status가 Success일 때만 GetWaypoints를 정상 경로로 사용합니다.
- Navigation mesh에서 시작점·목적지·문·계단의 연결을 확인했습니다.
- AgentRadius와 AgentHeight를 실제 NPC·통로 크기로 측정했습니다.
- 3,000 studs 거리, 20,000 node와 수직 범위 제한을 고려했습니다.
- NPC가 지면에 안정된 뒤 첫 경로를 계산합니다.
- PathWaypointAction.Jump와 게임 고유 Label 행동을 처리합니다.
- MoveToFinished false를 계산 실패와 구분하고 진행 거리를 기록합니다.
- Blocked index가 현재 index 앞쪽일 때만 재계산합니다.
- 새 경로 전에 이전 이벤트 연결과 작업을 정리합니다.
- 스트리밍 환경에서 서버와 클라이언트가 보는 geometry 차이를 확인했습니다.
- 작은 테스트 맵에서 변수 하나씩 추가해 회귀 테스트했습니다.
- NPC별 재계산 횟수·cooldown·실패 복구 경로를 정했습니다.
공식 출처·갱신일·적용 범위
아래 Roblox Creator Hub 1차 자료를 2026년 10월 11일 공개 HTML과 Markdown 원문으로 확인했습니다. Markdown 메타데이터의 last_updated는 Pathfinding 문서가 2026년 10월 8일 21:43:46 UTC, PathfindingService·Path·Humanoid API가 2026년 10월 8일 21:43:58 UTC, Streaming techniques가 2026년 10월 8일 21:44:08 UTC로 표시됐습니다. 이는 기능 출시일이 아니라 문서 최종 갱신 시각입니다.
- Pathfinding — Navigation mesh, CreatePath 매개변수, ComputeAsync·waypoint, Blocked index, modifier, 스트리밍 호환성과 공식 제한을 확인했습니다.
- PathfindingService API — CreatePath의 에이전트 매개변수와 서비스 적용 범위를 확인했습니다.
- Path API — ComputeAsync, GetWaypoints, Status와 Blocked 이벤트의 역할을 확인했습니다.
- Humanoid.MoveTo·MoveToFinished — NPC waypoint 이동, 도착 판정과 약 8초 timeout의 의미를 확인했습니다.
- Streaming techniques and conversion — 클라이언트 Pathfinding이 stream in된 geometry만 본다는 범위와 서버 질의 판단을 확인했습니다.
적용 범위: Roblox Studio에서 Humanoid 기반 NPC가 고정 또는 제한적으로 움직이는 목적지까지 PathfindingService 경로를 계산하고 따라가는 과정의 일반 진단입니다. 실제 Place의 NPC 모델, 충돌 그룹, 네트워크 소유권, 애니메이션, 문·엘리베이터 상태 머신, 동시 NPC 수와 서버 성능을 실행 측정한 결과가 아니며 특정 맵의 도착을 보장하지 않습니다. 공개 전 소유한 테스트 Place에서 최신 Creator Hub 문서, Server & Clients, Output과 MicroProfiler 지표를 기준으로 검증하세요.