콘텐츠로 이동

Unity NavMesh 고급 활용

Unity NavMesh는 AI 캐릭터의 경로 탐색을 위한 내비게이션 메쉬 시스템입니다. AI Navigation 패키지(com.unity.ai.navigation)를 사용하면 런타임 동적 베이킹, 복수 Surface, NavMeshLink로 수직 이동까지 구현할 수 있습니다.


// Package Manager: com.unity.ai.navigation 설치
// NavMeshSurface 컴포넌트를 지형 오브젝트에 추가
using Unity.AI.Navigation;
public class NavMeshManager : MonoBehaviour
{
[SerializeField] private NavMeshSurface _surface;
// 런타임 베이킹
public void BakeNavMesh()
{
_surface.BuildNavMesh();
}
// 부분 업데이트 (장애물 변경 후)
public void UpdateNavMesh(Bounds changedArea)
{
var sources = new List<NavMeshBuildSource>();
var markups = new List<NavMeshBuildMarkup>();
NavMeshBuilder.CollectSources(
changedArea,
_surface.layerMask,
_surface.useGeometry,
_surface.defaultArea,
markups,
sources);
NavMeshBuilder.UpdateNavMeshDataAsync(
_surface.navMeshData,
_surface.GetBuildSettings(),
sources,
changedArea);
}
}

[RequireComponent(typeof(NavMeshAgent))]
public class AdvancedNavAgent : MonoBehaviour
{
private NavMeshAgent _agent;
void Awake()
{
_agent = GetComponent<NavMeshAgent>();
// 물리와 NavMesh 분리: 루트 모션 사용 시
_agent.updatePosition = false;
_agent.updateRotation = false;
}
void Update()
{
// 경로 진행 중 커스텀 속도 제어
if (_agent.hasPath && !_agent.pathPending)
{
float remainingDist = _agent.remainingDistance;
// 목표 근접 시 감속
if (remainingDist < 2f)
_agent.speed = Mathf.Lerp(0.5f, 4f,
remainingDist / 2f);
}
}
// 루트 모션과 NavMesh 동기화
void OnAnimatorMove()
{
if (_agent.isOnNavMesh)
{
_agent.velocity = GetComponent<Animator>().deltaPosition / Time.deltaTime;
}
transform.position = _agent.nextPosition;
}
// 경로 존재 여부 확인
public bool CanReach(Vector3 target)
{
var path = new NavMeshPath();
return _agent.CalculatePath(target, path) &&
path.status == NavMeshPathStatus.PathComplete;
}
// 가장 가까운 NavMesh 위치 샘플링
public bool SamplePosition(Vector3 pos, out Vector3 result)
{
if (NavMesh.SamplePosition(pos, out var hit, 2f, NavMesh.AllAreas))
{
result = hit.position;
return true;
}
result = pos;
return false;
}
}

섹션 제목: “3. NavMeshLink — 점프/사다리 연결”
using Unity.AI.Navigation;
public class LadderLink : MonoBehaviour
{
[SerializeField] private Transform _bottom;
[SerializeField] private Transform _top;
void Start()
{
var link = gameObject.AddComponent<NavMeshLink>();
link.startPoint = _bottom.localPosition;
link.endPoint = _top.localPosition;
link.width = 1f;
link.bidirectional = true;
link.costModifier = 3f; // 사다리 이동 비용 증가
link.area = NavMesh.GetAreaFromName("Climb");
}
}
// 오프-메쉬 링크 커스텀 이동 처리
public class OffMeshLinkMover : MonoBehaviour
{
private NavMeshAgent _agent;
private bool _traversing;
void Update()
{
if (_agent.isOnOffMeshLink && !_traversing)
StartCoroutine(TraverseLink());
}
IEnumerator TraverseLink()
{
_traversing = true;
var link = _agent.currentOffMeshLinkData;
Vector3 start = link.startPos;
Vector3 end = link.endPos;
// 포물선 점프 애니메이션
float t = 0f;
while (t < 1f)
{
t += Time.deltaTime * 2f;
float height = Mathf.Sin(Mathf.PI * t) * 2f;
transform.position = Vector3.Lerp(start, end, t)
+ Vector3.up * height;
yield return null;
}
_agent.CompleteOffMeshLink();
_traversing = false;
}
}

4. 군중 시뮬레이션 — NavMeshObstacle

섹션 제목: “4. 군중 시뮬레이션 — NavMeshObstacle”
// 동적 장애물 (다른 에이전트나 이동 오브젝트)
public class DynamicObstacle : MonoBehaviour
{
private NavMeshObstacle _obstacle;
void Awake()
{
_obstacle = GetComponent<NavMeshObstacle>();
_obstacle.carving = true; // NavMesh 파괴
_obstacle.carvingMoveThreshold = 0.1f;
_obstacle.carvingTimeToStationary = 0.5f;
}
// 이동 중에는 carving 비활성화 (성능)
void OnEnable() => _obstacle.carving = true;
void OnDisable() => _obstacle.carving = false;
}
// 여러 에이전트 분산 목표
public class CrowdDirector : MonoBehaviour
{
[SerializeField] private NavMeshAgent[] _agents;
public void ScatterTo(Vector3 center, float radius)
{
foreach (var agent in _agents)
{
// 원 안의 랜덤 위치 샘플링
Vector2 offset = Random.insideUnitCircle * radius;
Vector3 target = center + new Vector3(offset.x, 0, offset.y);
if (NavMesh.SamplePosition(target, out var hit, 2f, NavMesh.AllAreas))
agent.SetDestination(hit.position);
}
}
}

// NavMesh Area 비용으로 경로 선호도 제어
public class AreaCostController : MonoBehaviour
{
void Start()
{
var agent = GetComponent<NavMeshAgent>();
// 물 지역은 5배 비용 (가능하면 우회)
int waterArea = NavMesh.GetAreaFromName("Water");
agent.SetAreaCost(waterArea, 5f);
// 도로는 0.5배 비용 (선호)
int roadArea = NavMesh.GetAreaFromName("Road");
agent.SetAreaCost(roadArea, 0.5f);
// 특정 영역 제외 (통과 불가)
agent.areaMask = NavMesh.AllAreas
& ~(1 << NavMesh.GetAreaFromName("Danger"));
}
}

NavMeshSurface의 런타임 베이킹으로 파괴 가능한 환경을 구현하고, NavMeshLink로 점프/사다리를 오프-메쉬 링크로 연결하세요. 군중 시뮬레이션에서는 NavMeshObstacle의 carving을 정지 시에만 활성화해 성능을 확보하고, areaMaskSetAreaCost로 에이전트별 선호 경로를 다르게 설정하면 현실적인 이동 패턴을 만들 수 있습니다.