내비메시는 바닥이 아니라 설 수 있는 자리다
Esc

    내비메시는 바닥이 아니라 설 수 있는 자리다

    2026년 9월 18일 수정–Unity격자

    씬에서 내비메시를 구워 놓고 보면 바닥 메시와 모양이 다릅니다. 벽에서 한 뼘씩 떨어져 있고, 모서리가 조금씩 뭉툭합니다. 기둥 주위는 실제보다 넓게 파여 있고, 좁은 복도는 아예 없어지기도 합니다.

    버그가 아닙니다. 내비메시는 바닥을 옮겨 그린 것이 아니라, 에이전트가 설 수 있는 자리를 계산해 둔 것입니다. 유니티 문서는 에이전트를 원기둥으로 보고, 그 원기둥이 설 수 있는 자리를 시험해서 걸을 수 있는 영역을 만든다고 적어 두고 있습니다.

    굽는 것은 세 단계다

    유니티 매뉴얼이 적어 둔 순서는 이렇습니다. 장면을 보켈로 래스터화하고, 걸을 수 있는 표면을 뽑아내고, 그것을 내비메시로 바꿉니다.

    보켈 540개로 시작해서 폴리곤 다섯 개가 남았습니다. 도형에 안 걸린 칸이 426개였는데, 반지름만큼 물러나고 나니 77개입니다. 여기가 핵심입니다.

    단계마다 무엇이 정해지는지 보면 이렇습니다.

    단계 하는 일 여기서 정해지는 것
    보켈로 덮기 장면 도형을 일정한 크기의 칸으로 바꿉니다 내비메시가 도형을 얼마나 정확히 따라가는가
    설 수 있는 칸 고르기 경사, 높이, 단차를 보고 못 서는 칸을 버립니다 에이전트가 오를 수 있는 곳
    반지름만큼 물러나기 벽에서 반지름만큼 안쪽으로 깎습니다 내비메시 경계가 벽이 아니라 에이전트 가운데의 한계가 됩니다
    덩어리로 나누고 묶기 이어진 칸을 볼록한 조각으로 만듭니다 런타임에 훑을 폴리곤

    내비메시 위의 한 점은 「거기 발을 디딜 수 있다」 가 아니라 「거기에 에이전트 가운데를 놓아도 된다」 는 뜻입니다. 벽에서 떨어져 보이는 것이 그래서입니다. 반지름을 0.5로 잡았다면 딱 0.5만큼 떨어져 있습니다.

    보켈 크기가 정확도와 시간을 같이 정한다

    보켈이 작을수록 도형을 정확히 따라가고, 굽는 값이 커집니다.

    유니티의 기본값은 에이전트 반지름 하나에 보켈 세 개입니다. 에이전트 너비로 치면 여섯 개입니다. 그리고 문서는 보켈을 절반으로 줄이면 메모리와 빌드 시간이 네 배가 된다고 못 박아 뒀습니다. 2D 로 자르는 값이라 제곱으로 듭니다.

    보켈 크기 결과
    기본 (반지름당 3) 대부분의 맵에서 쓰는 값
    더 잘게 좁은 통로가 살아납니다. 굽는 시간과 메모리가 제곱으로 늡니다
    더 굵게 빨리 구워지지만 모서리가 뭉툭해지고 얇은 구조가 사라집니다

    에이전트 반지름을 줄이면 보켈도 같이 작아집니다. 기본값이 반지름에 묶여 있어서입니다. 작은 적을 하나 추가했더니 굽는 시간이 몇 배가 되는 일이 여기서 생깁니다. 반지름만 바꿀 생각이었다면 보켈 크기를 먼저 고정해 두세요.

    에이전트 하나당 내비메시 하나

    반지름이 구워질 때 들어가므로, 반지름이 다른 에이전트는 같은 내비메시를 쓸 수 없습니다. 높이와 오를 수 있는 경사, 넘을 수 있는 단차도 마찬가지입니다. 이 넷이 에이전트 타입 하나를 이룹니다.

    설정 무엇을 정하나
    반지름 벽에서 얼마나 물러날지
    높이 어느 높이의 천장 아래를 지날 수 있는지
    최대 경사 어느 각도까지 오를 수 있는지
    단차 계단 한 칸을 얼마까지 그냥 넘을지

    사람과 거대 보스가 같이 다니는 게임이면 내비메시를 두 벌 굽습니다. 메모리도 두 배, 굽는 시간도 두 배입니다. 반대로 반지름을 억지로 맞춰 한 벌로 끝내는 것도 흔한 선택입니다. 큰 쪽에 맞추면 작은 유닛이 좁은 길을 못 쓰고, 작은 쪽에 맞추면 큰 유닛이 벽에 낍니다. 어느 쪽이 덜 눈에 띄는지로 정하는 문제입니다.

    좁은 통로가 사라지는 이유

    구운 결과에 있어야 할 복도가 없는 경우가 있습니다. 원인이 대개 둘 중 하나입니다.

    반지름만큼 물러나고 나니 남는 게 없는 경우. 폭이 반지름의 두 배보다 좁으면 통로가 통째로 사라집니다. 여기에 보켈 격자가 어긋나는 몫이 더 붙습니다. 유니티 문서는 반지름에 더해 보켈 크기의 네 배 정도 여유를 두라고 권합니다. 그것도 안 되면 그 자리는 링크로 넘기라고 합니다.

    작은 조각이라 잘려 나간 경우. 굽기에는 일정 넓이보다 작고 동떨어진 조각을 버리는 설정이 있습니다. 공중에 뜬 작은 발판이나 도달할 수 없는 구석을 치우는 용도입니다. 문서가 적어 둔 예외가 하나 있는데, 그 조각이 타일 경계에 걸쳐 있으면 안 잘립니다. 타일을 나눠서 병렬로 굽기 때문에, 자를 때 옆 타일을 아직 모릅니다. 같은 크기의 조각이 어떤 데서는 남고 어떤 데서는 사라지는 이유입니다.

    런타임에는 폴리곤을 훑는다

    구워 두고 나면 런타임 길찾기는 칸이 아니라 폴리곤 위에서 돕니다.

    1. 시작과 목표를 가장 가까운 폴리곤으로 옮깁니다. 내비메시 위가 아닌 지점을 넘기면 이 단계에서 어긋납니다
    2. 이웃 폴리곤을 훑어 목표 폴리곤까지 갑니다. 그렇게 나온 폴리곤의 줄이 통로입니다
    3. 에이전트는 통로 안에서 다음으로 보이는 코너를 향해 갑니다
    var path = new NavMeshPath();
    if (NavMesh.CalculatePath(transform.position, target, NavMesh.AllAreas, path)) {
        // corners 는 이미 뽑아낸 꺾이는 점입니다. 폴리곤 통로가 아닙니다
        for (int i = 1; i < path.corners.Length; i++)
            Debug.DrawLine(path.corners[i - 1], path.corners[i], Color.red);
    }

    path.status 를 같이 봐야 합니다. 목표에 못 닿으면 가장 가까운 데까지의 부분 경로가 담겨 오고, CalculatePath 자체는 참을 돌려줍니다. 이걸 안 보면 유닛이 벽 앞에서 멈춘 채 도착했다고 여깁니다.

    내비메시 위가 아닌 좌표를 목표로 넘길 때는 먼저 붙여 두는 편이 안전합니다.

    if (NavMesh.SamplePosition(clickPoint, out var hit, 2f, NavMesh.AllAreas))
        agent.SetDestination(hit.position);

    회피는 길찾기가 아니다

    에이전트가 실제로 움직이는 방향은 경로가 그대로 정하지 않습니다. 유니티 문서의 표현대로 회피 단계가 「가고 싶은 방향」 과 「부딪히지 않는 것」 사이에서 새 속도를 고릅니다. 다른 에이전트뿐 아니라 내비메시의 가장자리도 그 계산에 들어갑니다.

    그래서 경로는 멀쩡한데 유닛이 이상하게 움직이는 일이 생깁니다. 이때 볼 것은 경로가 아니라 회피 설정입니다.

    • 회피 품질을 낮추면 계산이 싸지는 대신 서로 겹쳐 지나갑니다. 유닛이 수백이면 대개 이쪽을 씁니다
    • 우선순위로 누가 비켜 줄지 정합니다. 같은 값이면 서로 비키다가 둘 다 멈추는 모양이 나옵니다
    • 멈춰 있는 유닛은 장애물로 바꿔 두는 편이 낫습니다. 회피 대상으로 두면 계속 계산에 들어갑니다

    런타임에 지형이 바뀌면

    문이 닫히거나 건물이 세워지면 내비메시를 고쳐야 합니다. 두 가지 길이 있고 값이 다릅니다.

    카빙. 장애물 컴포넌트가 내비메시에 닿는 부분을 찾아 구멍을 냅니다. 문서가 대놓고 비싼 연산이라고 적어 둔 부분입니다. 움직이는 물체마다 켜 두면 프레임이 그대로 무너집니다. 움직이는 동안은 끄고 멈춘 뒤에만 켜는 것이 기본입니다.

    다시 굽기. 표면 컴포넌트를 런타임에 다시 구울 수 있습니다. 구역을 나눠 놓고 바뀐 구역만 다시 굽는 방식이 흔합니다. 한 번에 다 구우면 그 프레임이 통째로 날아갑니다.

    장애물이 잠깐 지나가는 것뿐이라면 둘 다 안 하는 것이 맞습니다. 회피에 맡기면 됩니다. 내비메시를 고치는 것은 길 자체가 막히거나 열릴 때입니다.

    이어지지 않은 곳은 링크로

    점프, 사다리, 난간 넘기처럼 표면이 이어지지 않은 이동은 구워지지 않습니다. 그런 자리는 링크 컴포넌트로 「여기로 건너갈 수 있다」 를 직접 알려 줍니다. 길찾기는 그 링크를 간선 하나로 취급합니다.

    새 프로젝트에서는 NavMeshLink 를 씁니다. 예전의 OffMeshLink 는 더 이상 에디터에서 추가할 수 없습니다. 쓰던 프로젝트는 그대로 돌아가지만, 새로 놓을 수는 없습니다.

    링크를 지날 때의 움직임은 직접 만들어야 합니다. 기본 동작은 그냥 순간이동에 가깝습니다. agent.isOnOffMeshLink 를 보고 그 구간만 코루틴으로 포물선을 그리는 식이 흔한 처리입니다.

    높이는 따로 있다

    내비메시는 근사입니다. 계단을 구우면 경사로처럼 뭉개져서 나오고, 에이전트가 계단 위를 미끄러지듯 지나갑니다.

    그래서 높이 메시를 따로 굽는 설정이 있습니다. 길찾기는 여전히 뭉갠 폴리곤으로 하고, 에이전트를 그 자리에 놓을 때만 정확한 높이를 봅니다. 문서가 적어 둔 대로 메모리와 굽는 시간을 더 씁니다.

    계단이 있는 맵에서 유닛이 공중에 뜨거나 파묻힌다면 여기부터 보면 됩니다.

    정리

    내비메시는 바닥을 옮겨 그린 것이 아니라 에이전트가 설 수 있는 자리를 미리 계산해 둔 결과입니다.

    • 굽기는 보켈로 덮고, 설 수 있는 칸을 고르고, 반지름만큼 물러나고, 볼록한 조각으로 묶는 과정입니다
    • 내비메시 경계는 벽이 아니라 에이전트 가운데의 한계입니다. 벽에서 떨어져 보이는 것이 정상입니다
    • 보켈은 기본이 반지름당 세 개이고, 절반으로 줄이면 메모리와 시간이 네 배입니다
    • 반지름·높이·경사·단차가 구워지므로 에이전트 타입마다 내비메시가 한 벌씩 필요합니다
    • 런타임은 폴리곤 통로를 찾고 그 안에서 코너를 뽑아 따라갑니다. 부분 경로를 성공으로 오해하지 마세요
    • 유닛이 이상하게 움직이면 경로가 아니라 회피 설정을 보세요
    • 카빙은 비쌉니다. 길이 진짜로 막히고 열릴 때만 씁니다