Unity NavMesh AI 네비게이션 시스템 완벽 가이드

CokeBear·2025년 8월 8일
0

Unity NavMesh AI 네비게이션 시스템 완벽 가이드

Unity의 NavMesh(Navigation Mesh) 시스템은 AI 캐릭터들이 지형을 자동으로 탐색하고 장애물을 피해 목적지까지 이동할 수 있게 해주는 강력한 도구입니다. 이번 글에서는 NavMesh 시스템의 기본 설정부터 실제 AI 구현까지 단계별로 알아보겠습니다.

NavMesh는 Navigation Mesh의 줄임말로, AI 에이전트가 이동할 수 있는 영역을 미리 계산해둔 메쉬입니다. 이 시스템을 통해 AI는 복잡한 지형에서도 최적의 경로를 찾아 자동으로 이동할 수 있습니다.

주요 특징

  • 자동 경로 찾기: A* 알고리즘 기반의 효율적인 길찾기
  • 장애물 회피: 동적 및 정적 장애물 자동 회피
  • 점프 및 낙하: 플랫폼 간 이동 지원
  • 성능 최적화: 미리 계산된 메쉬로 실시간 성능 향상

1. AI Navigation 패키지 설치

먼저 Unity에서 AI Navigation 패키지를 설치해야 합니다.

설치 단계

  1. WindowPackage Manager 열기
  2. Unity Registry 선택
  3. 검색창에 "nav" 입력
  4. AI Navigation 패키지 찾기
  5. Install 버튼 클릭
Package Manager → Unity Registry → "nav" 검색 → AI Navigation → Install

2. NavMesh Surface 설정

2-1. 기본 지면 설정

NavMesh 시스템의 첫 번째 단계는 AI가 걸을 수 있는 표면을 정의하는 것입니다.

// 기본 지면 오브젝트(Plane) 선택
// Add Component → NavMesh Surface

2-2. NavMesh 베이킹

  1. Plane 오브젝트 선택
  2. Add ComponentNavMesh Surface
  3. Bake 버튼 클릭

베이킹 후 파란색 영역이 표시되지 않으면:

  • Scene 뷰 상단의 기즈모 버튼 클릭
  • NavMesh 체크박스 활성화

2-3. 걸을 수 있는 영역 확장

기본적으로는 Plane만 걸을 수 있는 영역으로 설정됩니다. 다른 오브젝트들도 포함시키려면:

  1. Hierarchy에서 Shift 키를 누른 채로 GroundObstacles 선택
  2. Inspector에서 Navigation 탭으로 이동
  3. Navigation Static 체크박스 활성화
  4. NavMesh Surface에서 다시 Bake 클릭

3. NavMesh Agent 설정 최적화

3-1. 기본 에이전트 설정

WindowAINavigation에서 에이전트 설정을 조정할 수 있습니다.

Navigation Window 설정:
- Agent Radius: 0.25 (에이전트의 반지름)
- Agent Height: 0.7 (에이전트의 키)
- Max Slope: 35 (오를 수 있는 최대 경사도)
- Step Height: 0.1 (오를 수 있는 계단 높이)

3-2. 최적화된 설정 예시

// 권장 설정값
Agent Radius: 0.25f     // 좁은 공간 통과 가능
Agent Height: 0.7f      // 일반적인 캐릭터 크기
Max Slope: 35f          // 적당한 경사면 이동
Step Height: 0.1f       // 작은 턱 극복 가능

4. 기본 AI 스크립트 구현

4-1. 간단한 NavMesh Agent 스크립트

using UnityEngine;
using UnityEngine.AI;

public class SimpleAI : MonoBehaviour
{
    [Header("Navigation")]
    public NavMeshAgent agent;
    public Transform target;
    
    private void Start()
    {
        // NavMeshAgent 컴포넌트 자동 할당
        if (agent == null)
            agent = GetComponent<NavMeshAgent>();
    }
    
    private void Update()
    {
        // 타겟이 설정되어 있으면 해당 위치로 이동
        if (target != null)
        {
            agent.SetDestination(target.position);
        }
    }
}

4-2. 개선된 AI 스크립트

using UnityEngine;
using UnityEngine.AI;

public class AdvancedAI : MonoBehaviour
{
    [Header("Navigation")]
    public NavMeshAgent agent;
    public Transform target;
    
    [Header("AI Settings")]
    public float detectionRange = 10f;
    public float stopDistance = 2f;
    
    private void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        
        // 에이전트 설정
        agent.stoppingDistance = stopDistance;
    }
    
    private void Update()
    {
        if (target != null)
        {
            float distanceToTarget = Vector3.Distance(transform.position, target.position);
            
            // 탐지 범위 내에 있을 때만 추적
            if (distanceToTarget <= detectionRange)
            {
                agent.SetDestination(target.position);
            }
            else
            {
                // 범위를 벗어나면 정지
                agent.ResetPath();
            }
        }
    }
    
    private void OnDrawGizmosSelected()
    {
        // 탐지 범위 시각화
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(transform.position, detectionRange);
        
        // 정지 거리 시각화
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(transform.position, stopDistance);
    }
}

5. 점프 및 낙하 설정

5-1. Off Mesh Link를 통한 점프 구현

플랫폼 간 점프나 낙하가 필요한 경우 Off Mesh Link를 사용합니다.

자동 링크 생성

  1. NavMesh Surface 선택
  2. Advanced 섹션 펼치기
  3. Generate Links 체크박스 활성화
  4. Jump Distance: 1.0 설정 (점프 가능 거리)
  5. Drop Height: 3.0 설정 (낙하 가능 높이)
  6. Bake 다시 실행

수동 링크 생성

// Off Mesh Link 컴포넌트를 수동으로 추가
// Start Point와 End Point를 설정하여 정확한 점프 지점 지정

5-2. 점프 애니메이션 연동

public class AIAnimationController : MonoBehaviour
{
    private NavMeshAgent agent;
    private Animator animator;
    
    private void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        animator = GetComponent<Animator>();
    }
    
    private void Update()
    {
        // 이동 속도에 따른 애니메이션
        float speed = agent.velocity.magnitude;
        animator.SetFloat("Speed", speed);
        
        // Off Mesh Link 사용 중인지 확인 (점프/낙하)
        if (agent.isOnOffMeshLink)
        {
            animator.SetTrigger("Jump");
        }
    }
}

6. 다중 AI 에이전트 관리

6-1. 여러 AI 동시 제어

using System.Collections.Generic;
using UnityEngine;

public class AIManager : MonoBehaviour
{
    [Header("AI Management")]
    public List<GameObject> aiPrefabs;
    public Transform[] spawnPoints;
    public Transform commonTarget;
    
    [Header("Spawn Settings")]
    public int aiCount = 10;
    
    private List<GameObject> spawnedAIs = new List<GameObject>();
    
    private void Start()
    {
        SpawnMultipleAIs();
    }
    
    private void SpawnMultipleAIs()
    {
        for (int i = 0; i < aiCount; i++)
        {
            // 랜덤 스폰 지점 선택
            Transform spawnPoint = spawnPoints[Random.Range(0, spawnPoints.Length)];
            
            // 랜덤 AI 프리팹 선택
            GameObject aiPrefab = aiPrefabs[Random.Range(0, aiPrefabs.Length)];
            
            // AI 생성
            GameObject newAI = Instantiate(aiPrefab, spawnPoint.position, spawnPoint.rotation);
            
            // 타겟 설정
            SimpleAI aiScript = newAI.GetComponent<SimpleAI>();
            if (aiScript != null)
            {
                aiScript.target = commonTarget;
            }
            
            spawnedAIs.Add(newAI);
        }
    }
    
    public void ChangeAllTargets(Transform newTarget)
    {
        foreach (GameObject ai in spawnedAIs)
        {
            SimpleAI aiScript = ai.GetComponent<SimpleAI>();
            if (aiScript != null)
            {
                aiScript.target = newTarget;
            }
        }
    }
}

7. 성능 최적화 팁

7-1. NavMesh Agent 최적화

public class OptimizedAI : MonoBehaviour
{
    private NavMeshAgent agent;
    private float updateInterval = 0.1f; // 0.1초마다 업데이트
    private float lastUpdateTime;
    
    private void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        
        // 성능 최적화 설정
        agent.acceleration = 8f;        // 가속도
        agent.angularSpeed = 120f;      // 회전 속도
        agent.obstacleAvoidanceType = ObstacleAvoidanceType.LowQualityObstacleAvoidance;
    }
    
    private void Update()
    {
        // 일정 간격으로만 경로 업데이트
        if (Time.time - lastUpdateTime >= updateInterval)
        {
            UpdateNavigation();
            lastUpdateTime = Time.time;
        }
    }
    
    private void UpdateNavigation()
    {
        // 실제 네비게이션 로직
        if (target != null && agent.enabled)
        {
            agent.SetDestination(target.position);
        }
    }
}

7-2. 거리 기반 LOD 시스템

public class AILODManager : MonoBehaviour
{
    [Header("LOD Settings")]
    public float highDetailDistance = 20f;
    public float mediumDetailDistance = 50f;
    
    private Transform player;
    private NavMeshAgent agent;
    
    private void Start()
    {
        player = FindObjectOfType<Player>().transform;
        agent = GetComponent<NavMeshAgent>();
    }
    
    private void Update()
    {
        float distanceToPlayer = Vector3.Distance(transform.position, player.position);
        
        // 거리에 따른 업데이트 빈도 조절
        if (distanceToPlayer <= highDetailDistance)
        {
            // 고품질: 매 프레임 업데이트
            agent.enabled = true;
            GetComponent<Animator>().enabled = true;
        }
        else if (distanceToPlayer <= mediumDetailDistance)
        {
            // 중품질: 낮은 빈도 업데이트
            agent.enabled = true;
            GetComponent<Animator>().enabled = false;
        }
        else
        {
            // 저품질: 네비게이션 비활성화
            agent.enabled = false;
            GetComponent<Animator>().enabled = false;
        }
    }
}

8. 고급 기능 구현

8-1. 순찰 시스템

using System.Collections;
using UnityEngine;

public class PatrolAI : MonoBehaviour
{
    [Header("Patrol Settings")]
    public Transform[] patrolPoints;
    public float waitTime = 2f;
    public bool randomPatrol = false;
    
    private NavMeshAgent agent;
    private int currentPatrolIndex = 0;
    private bool isWaiting = false;
    
    private void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        
        if (patrolPoints.Length > 0)
        {
            agent.SetDestination(patrolPoints[0].position);
        }
    }
    
    private void Update()
    {
        // 목적지에 도달했는지 확인
        if (!agent.pathPending && agent.remainingDistance < 0.5f && !isWaiting)
        {
            StartCoroutine(WaitAndMoveToNext());
        }
    }
    
    private IEnumerator WaitAndMoveToNext()
    {
        isWaiting = true;
        yield return new WaitForSeconds(waitTime);
        
        // 다음 순찰 지점 결정
        if (randomPatrol)
        {
            currentPatrolIndex = Random.Range(0, patrolPoints.Length);
        }
        else
        {
            currentPatrolIndex = (currentPatrolIndex + 1) % patrolPoints.Length;
        }
        
        // 다음 목적지로 이동
        agent.SetDestination(patrolPoints[currentPatrolIndex].position);
        isWaiting = false;
    }
}

8-2. 상태 기반 AI

using UnityEngine;

public enum AIState
{
    Idle,
    Patrol,
    Chase,
    Attack,
    Return
}

public class StateMachineAI : MonoBehaviour
{
    [Header("AI Settings")]
    public Transform player;
    public float detectionRange = 10f;
    public float attackRange = 2f;
    public float loseTargetRange = 15f;
    
    [Header("Patrol")]
    public Transform[] patrolPoints;
    
    private NavMeshAgent agent;
    private AIState currentState = AIState.Patrol;
    private Vector3 initialPosition;
    private int patrolIndex = 0;
    
    private void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        initialPosition = transform.position;
        
        if (patrolPoints.Length > 0)
        {
            agent.SetDestination(patrolPoints[0].position);
        }
    }
    
    private void Update()
    {
        float distanceToPlayer = Vector3.Distance(transform.position, player.position);
        
        switch (currentState)
        {
            case AIState.Patrol:
                HandlePatrolState(distanceToPlayer);
                break;
                
            case AIState.Chase:
                HandleChaseState(distanceToPlayer);
                break;
                
            case AIState.Attack:
                HandleAttackState(distanceToPlayer);
                break;
                
            case AIState.Return:
                HandleReturnState();
                break;
        }
    }
    
    private void HandlePatrolState(float distanceToPlayer)
    {
        // 플레이어 탐지
        if (distanceToPlayer <= detectionRange)
        {
            ChangeState(AIState.Chase);
            return;
        }
        
        // 순찰 지점 도달 확인
        if (!agent.pathPending && agent.remainingDistance < 0.5f)
        {
            patrolIndex = (patrolIndex + 1) % patrolPoints.Length;
            agent.SetDestination(patrolPoints[patrolIndex].position);
        }
    }
    
    private void HandleChaseState(float distanceToPlayer)
    {
        // 공격 범위 내 진입
        if (distanceToPlayer <= attackRange)
        {
            ChangeState(AIState.Attack);
            return;
        }
        
        // 플레이어를 놓침
        if (distanceToPlayer > loseTargetRange)
        {
            ChangeState(AIState.Return);
            return;
        }
        
        // 플레이어 추적
        agent.SetDestination(player.position);
    }
    
    private void HandleAttackState(float distanceToPlayer)
    {
        // 공격 범위를 벗어남
        if (distanceToPlayer > attackRange)
        {
            ChangeState(AIState.Chase);
            return;
        }
        
        // 공격 로직 (애니메이션, 데미지 등)
        // 여기서 실제 공격 구현
    }
    
    private void HandleReturnState()
    {
        // 초기 위치로 복귀
        agent.SetDestination(initialPosition);
        
        if (Vector3.Distance(transform.position, initialPosition) < 1f)
        {
            ChangeState(AIState.Patrol);
        }
    }
    
    private void ChangeState(AIState newState)
    {
        currentState = newState;
        Debug.Log($"AI State changed to: {newState}");
    }
}

9. 문제 해결 및 디버깅

9-1. 일반적인 문제들

// 해결 방법:
// 1. 오브젝트가 Navigation Static으로 설정되어 있는지 확인
// 2. NavMesh Surface 컴포넌트가 올바르게 설정되어 있는지 확인
// 3. Bake 버튼을 다시 클릭

AI가 이동하지 않는 경우

public class NavMeshDebugger : MonoBehaviour
{
    private NavMeshAgent agent;
    
    private void Start()
    {
        agent = GetComponent<NavMeshAgent>();
    }
    
    private void Update()
    {
        // 디버그 정보 출력
        if (agent != null)
        {
            Debug.Log($"Agent enabled: {agent.enabled}");
            Debug.Log($"Has path: {agent.hasPath}");
            Debug.Log($"Path status: {agent.pathStatus}");
            Debug.Log($"Remaining distance: {agent.remainingDistance}");
        }
    }
}

9-2. 성능 모니터링

using UnityEngine;

public class NavMeshProfiler : MonoBehaviour
{
    [Header("Performance Monitoring")]
    public bool showDebugInfo = true;
    
    private NavMeshAgent[] allAgents;
    
    private void Start()
    {
        allAgents = FindObjectsOfType<NavMeshAgent>();
    }
    
    private void OnGUI()
    {
        if (showDebugInfo)
        {
            GUILayout.BeginArea(new Rect(10, 10, 300, 200));
            GUILayout.Label($"Active NavMesh Agents: {allAgents.Length}");
            
            int activeAgents = 0;
            foreach (var agent in allAgents)
            {
                if (agent.enabled && agent.gameObject.activeInHierarchy)
                    activeAgents++;
            }
            
            GUILayout.Label($"Enabled Agents: {activeAgents}");
            GUILayout.Label($"FPS: {1f / Time.unscaledDeltaTime:F1}");
            GUILayout.EndArea();
        }
    }
}

결론

Unity의 NavMesh 시스템은 복잡한 AI 내비게이션을 간단하게 구현할 수 있게 해주는 강력한 도구입니다. 기본적인 설정부터 고급 상태 머신까지, 이 가이드를 통해 여러분만의 지능적인 AI 시스템을 구축할 수 있을 것입니다.

핵심 포인트:

  • 올바른 NavMesh 베이킹이 모든 것의 기초
  • 성능 최적화를 통한 다중 AI 관리
  • 상태 기반 시스템으로 복잡한 AI 행동 구현
  • 디버깅 도구를 활용한 문제 해결

이제 여러분의 게임에 생동감 넘치는 AI 캐릭터들을 추가해보세요!

profile
back end developer

0개의 댓글