Unity Term Book
Advanced Techniques

NavMesh

NavMesh (Navigation Mesh) là lưới đi được bake từ geometry của scene, giúp AI Agent tìm đường, tránh vật cản và di chuyển tự động trên toàn bộ màn chơi.

Hãy tưởng tượng...

NavMesh như bản đồ đường bộ nhưng trong 3D world. Trước khi game chạy, Unity "vẽ" những vùng có thể đi được (màu xanh) và những vùng bị chặn (tường, vật cản). Khi AI cần đi từ A đến B, nó tra bản đồ này và dùng thuật toán A-star để tìm đường tối ưu — tự động đi vòng qua tường, leo cầu thang, tránh vực. NavMeshAgent là "tài xế GPS" theo bản đồ đó.

Khái niệm chi tiết

NavMesh Baking: Unity phân tích static geometry của scene (đánh dấu “Navigation Static”) để tạo mesh đi được. Tham số quan trọng: Agent Radius (khoảng cách tối thiểu với tường — agent không thể đi qua khe nhỏ hơn 2×radius), Agent Height (chiều cao đầu — không chui dưới trần thấp), Max Slope (góc dốc tối đa có thể leo), Step Height (bậc thang tối đa). Bake qua Window → AI → Navigation → Bake.

NavMeshAgent là component gắn lên NPC/AI — tự động query NavMesh và di chuyển. API chính: agent.SetDestination(target) để bắt đầu pathfinding; agent.remainingDistance kiểm tra còn bao xa; agent.pathStatus (Complete/Partial/Invalid). Thuộc tính speed, acceleration, stoppingDistance điều khiển chuyển động.

NavMeshObstacle là vật cản động — không cần rebake. Bật Carve để “đào” lỗ thủng trong NavMesh realtime (tốn CPU hơn), hoặc tắt Carve để chỉ dùng avoidance (agent đi vòng nhưng NavMesh không thay đổi). Phù hợp cho cánh cửa mở/đóng, xe di chuyển.

Cho dynamic NavMesh (cần rebake lúc runtime), dùng NavMesh Components package (GitHub Unity-Technologies) với NavMeshSurface — có thể bake partial region và update incremental. Unity 6 tích hợp NavMesh đa surface vào built-in package.

Các thành phần NavMesh System

🗺️

NavMesh

Lưới "vùng đi được" được bake từ scene geometry. Static, không đổi sau bake.

Navigation Static flag
🤖

NavMeshAgent

Component trên AI — query path, di chuyển tự động, tránh va chạm với agent khác (avoidance).

SetDestination(target)
🚧

NavMeshObstacle

Vật cản dynamic — không cần rebake. Carve mode đào lỗ thủng vào NavMesh runtime.

Carve = true/false

Hướng dẫn thực hành

1

Đánh dấu Static geometry

Chọn tất cả mesh tĩnh (tường, sàn, platform) → Inspector → Static dropdown → Navigation Static (và bật Contribute GI nếu cần lightmap).

2

Bake NavMesh

Window → AI → Navigation → Bake tab → điều chỉnh Agent Radius/Height/Slope → nhấn Bake. NavMesh màu xanh xuất hiện trong Scene view.

3

Add NavMeshAgent lên NPC

Add Component → NavMeshAgent. Khớp Agent Size với bake settings. Tắt Rigidbody gravity nếu có (NavMeshAgent tự quản lý di chuyển).

4

Set Destination từ script

agent.SetDestination(target.position). Check agent.remainingDistance <= agent.stoppingDistance để biết đã đến nơi.

5

Thêm NavMeshObstacle cho vật cản động

Add Component → NavMeshObstacle → Shape: Capsule/Box → bật Carve nếu cần cập nhật NavMesh realtime (cửa, xe đỗ).

Trình mô phỏng tương tác

Click vào bản đồ để đặt điểm đến — Agent tìm đường qua NavMesh dùng A*.

Click để đặt đích
NavMesh Agent Obstacle Path

Ví dụ Code

Cơ bản

NavMeshAgent AI đơn giản — chase player và dừng ở stopping distance.

using UnityEngine;
using UnityEngine.AI;

public class EnemyAI : MonoBehaviour
{
  public Transform player;
  public float chaseRange   = 15f;
  public float attackRange  = 2f;
  public float updateRate   = 0.2f; // Cập nhật path mỗi 0.2s

  NavMeshAgent agent;
  Animator anim;
  float nextUpdate;

  void Awake()
  {
      agent = GetComponent<NavMeshAgent>();
      anim  = GetComponent<Animator>();
  }

  void Update()
  {
      float dist = Vector3.Distance(transform.position, player.position);

      if (dist < chaseRange && Time.time >= nextUpdate)
      {
          nextUpdate = Time.time + updateRate;
          agent.SetDestination(player.position);
      }

      // Drive Animator theo tốc độ agent
      anim.SetFloat("Speed", agent.velocity.magnitude);

      if (dist < attackRange)
      {
          agent.ResetPath(); // Dừng di chuyển
          anim.SetTrigger("Attack");
      }
  }
}

Ví dụ Code

Nâng cao

Patrol AI với NavMesh — tuần tra nhiều waypoint và phát hiện player.

using UnityEngine;
using UnityEngine.AI;

public class PatrolAI : MonoBehaviour
{
  public Transform[] waypoints;
  public float detectRange = 8f;
  public Transform player;

  NavMeshAgent agent;
  int current = 0;
  enum State { Patrol, Chase, Investigate }
  State state = State.Patrol;

  void Awake() => agent = GetComponent<NavMeshAgent>();

  void Start() => GotoNextWaypoint();

  void Update()
  {
      float dist = Vector3.Distance(transform.position, player.position);

      switch (state)
      {
          case State.Patrol:
              if (dist < detectRange) { state = State.Chase; break; }
              if (!agent.pathPending && agent.remainingDistance < 0.5f)
                  GotoNextWaypoint();
              break;

          case State.Chase:
              agent.SetDestination(player.position);
              if (dist > detectRange * 1.5f)
                  state = State.Investigate;
              break;

          case State.Investigate:
              if (!agent.pathPending && agent.remainingDistance < 0.5f)
              { state = State.Patrol; GotoNextWaypoint(); }
              break;
      }
  }

  void GotoNextWaypoint()
  {
      if (waypoints.Length == 0) return;
      agent.SetDestination(waypoints[current].position);
      current = (current + 1) % waypoints.Length;
  }
}

📌 Ghi nhớ nhanh

  • NavMesh = bản đồ đi được, bake từ static geometry
  • SetDestination() mỗi frame tốn CPU — throttle với coroutine
  • remainingDistance: khoảng cách còn lại theo path
  • NavMeshObstacle + Carve = dynamic hole runtime
  • Tắt Rigidbody.gravity khi dùng NavMeshAgent

⚠️ Lỗi thường gặp

  • ❌ Agent không di chuyển dù SetDestination đúng

    Destination nằm ngoài NavMesh, hoặc NavMesh chưa bake

    ✅ Dùng NavMesh.SamplePosition() để tìm điểm gần nhất trên mesh

  • ❌ Agent đi qua tường hoặc bị kẹt

    Agent Radius trong NavMeshAgent nhỏ hơn khi bake

    ✅ Khớp Agent Radius trong NavMeshAgent == Agent Radius khi bake