Unity NavMesh 고급 활용
Unity NavMesh는 AI 캐릭터의 경로 탐색을 위한 내비게이션 메쉬 시스템입니다. AI Navigation 패키지(com.unity.ai.navigation)를 사용하면 런타임 동적 베이킹, 복수 Surface, NavMeshLink로 수직 이동까지 구현할 수 있습니다.
1. AI Navigation 패키지 설정
섹션 제목: “1. AI Navigation 패키지 설정”// 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); }}2. NavMeshAgent 고급 제어
섹션 제목: “2. NavMeshAgent 고급 제어”[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 — 점프/사다리 연결
섹션 제목: “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); } }}5. 멀티 에이리어 타입 활용
섹션 제목: “5. 멀티 에이리어 타입 활용”// 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을 정지 시에만 활성화해 성능을 확보하고, areaMask와 SetAreaCost로 에이전트별 선호 경로를 다르게 설정하면 현실적인 이동 패턴을 만들 수 있습니다.