NavMesh и AI — поиск пути и простой ИИ-противник
Navigation Mesh, NavMeshAgent, базовая стейт-машина для врага.
3D-уровень редко состоит из ровного пола. Враг должен обойти стол, спуститься по лестнице, найти
короткий путь к игроку и не свалиться с обрыва. Считать это вручную через Physics.Raycast —
кошмар. В Unity есть NavMesh — встроенная система навигации, которая решает 90% задач AI-движения
бесплатно.
NavMesh — “карта проходимости”
NavMesh — это запечённая упрощённая геометрия пола, по которой умеют ходить агенты. Unity сканирует вашу сцену и собирает полигональную сетку всех поверхностей, на которых можно стоять. Из этой сетки строится граф для алгоритма поиска пути (A* под капотом).
В Unity 6 рекомендованный путь — пакет AI Navigation (он же com.unity.ai.navigation). Старый
встроенный NavMesh (через Window → Navigation в Unity ≤ 2021) сохранился для совместимости, но
новый пакет даёт компонентный воркфлоу: NavMeshSurface, NavMeshModifier, NavMeshLink.
Базовый сценарий запекания
- Соберите статичную геометрию пола и стен.
- Добавьте на пустой GameObject компонент NavMeshSurface.
- В Agent Type выберите профиль (по умолчанию “Humanoid”: радиус 0.5, высота 2, max slope 45°, step height 0.4).
- Нажмите Bake. Появится синий полупрозрачный mesh в Scene view — это и есть NavMesh.
__________
/ \ ← NavMesh покрывает пол
/ Comfy \
| Room |
\ [agent]_/
\__________/
Стены, столбы, ямы → "дыры" в NavMesh
В новом проекте используйте NavMeshSurface — он позволяет иметь несколько мешей в сцене,
запекать рантайм (для процедурных уровней), и не зависит от глобальной настройки сцены. Старое
меню Window → Navigation продолжает работать, но это deprecated-путь.
NavMeshAgent — движение по мешу
NavMeshAgent — компонент, превращающий GameObject в “пешехода” по NavMesh:
using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(NavMeshAgent))]
public class Chaser : MonoBehaviour
{
[SerializeField] private Transform target;
private NavMeshAgent _agent;
private void Awake() => _agent = GetComponent<NavMeshAgent>();
private void Update() {
if (target != null) {
_agent.SetDestination(target.position);
}
}
}
Главные поля NavMeshAgent:
- Speed — скорость м/с.
- Angular Speed — скорость поворота в градусах/с.
- Acceleration — м/с².
- Stopping Distance — на каком расстоянии до цели агент остановится. Для атаки в ближнем бою ставят что-то вроде 1.5 м.
- Auto Braking — плавно тормозит при приближении к цели.
- Obstacle Avoidance Type — приоритет в локальном расступании, когда несколько агентов встречаются.
NavMeshObstacle — динамические препятствия
Двери, ящики, поваленные деревья — динамические препятствия. NavMeshObstacle бывает двух режимов:
- Без carve — агенты обходят препятствие через avoidance, но NavMesh не меняется. Дёшево.
- С carve = true — препятствие “вырезает” дыру в NavMesh, агенты строят новый путь. Дороже, но корректно для закрытых дверей и больших препятствий.
NavMeshLink — мостики и прыжки
Стандартный NavMesh не умеет в “телепорты”: лестницу с разрывом, прыжок через пропасть, лифт.
NavMeshLink соединяет два места — агент пересечёт его как обычное ребро графа.
Простой стейт-машина AI
Перейдём от “ходит к цели” к минимально живому врагу: патрулирует, заметил игрока — преследует, догнал — атакует.
using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(NavMeshAgent))]
public class EnemyAI : MonoBehaviour
{
private enum State { Patrol, Chase, Attack }
[SerializeField] private Transform[] patrolPoints;
[SerializeField] private Transform player;
[SerializeField] private float sightRange = 12f;
[SerializeField] private float attackRange = 2f;
[SerializeField] private float attackCooldown = 1.2f;
private NavMeshAgent _agent;
private State _state = State.Patrol;
private int _patrolIndex;
private float _nextAttackTime;
private void Awake() => _agent = GetComponent<NavMeshAgent>();
private void Start() {
if (patrolPoints.Length > 0) {
_agent.SetDestination(patrolPoints[0].position);
}
}
private void Update() {
float distToPlayer = Vector3.Distance(transform.position, player.position);
switch (_state) {
case State.Patrol:
if (distToPlayer < sightRange && HasLineOfSight()) {
_state = State.Chase;
} else if (!_agent.pathPending && _agent.remainingDistance < 0.5f) {
_patrolIndex = (_patrolIndex + 1) % patrolPoints.Length;
_agent.SetDestination(patrolPoints[_patrolIndex].position);
}
break;
case State.Chase:
_agent.SetDestination(player.position);
if (distToPlayer < attackRange) _state = State.Attack;
else if (distToPlayer > sightRange * 1.5f) _state = State.Patrol;
break;
case State.Attack:
_agent.SetDestination(transform.position); // стоим на месте
if (Time.time >= _nextAttackTime) {
Attack();
_nextAttackTime = Time.time + attackCooldown;
}
if (distToPlayer > attackRange + 0.5f) _state = State.Chase;
break;
}
}
private bool HasLineOfSight() {
Vector3 dir = player.position - transform.position;
if (Physics.Raycast(transform.position + Vector3.up, dir.normalized, out RaycastHit hit, sightRange)) {
return hit.transform == player;
}
return false;
}
private void Attack() {
// Сюда — анимация атаки и нанесение урона через ваш Health
if (player.TryGetComponent<Health>(out var hp)) hp.TakeDamage(10);
}
}
Обратите внимание: переход обратно из Chase в Patrol использует sightRange * 1.5f, а из Chase в
Attack — attackRange, тогда как из Attack в Chase — attackRange + 0.5f. Это гистерезис:
пороги входа и выхода не совпадают, чтобы враг не “дёргался” между состояниями ровно на границе.
Простой приём, заметно повышает ощущение цельности AI.
Что улучшить
- Поведенческие деревья (Behavior Tree) — для AI сложнее 3 состояний. Готовые пакеты — Behavior Designer, или новый встроенный Unity Behavior (в Unity 6 пришла официальная Behavior Trees система).
- Memory & senses — пары “вижу/слышу/помню”, где враг помнит последнюю позицию игрока ещё несколько секунд.
- Group coordination — несколько врагов делят зоны атаки через
NavMeshAgent.avoidancePriority. - Cover system — поиск точек укрытия через NavMesh.SamplePosition и Raycast.
В следующей главе — Particle System и VFX Graph, чтобы враг при смерти эффектно разлетелся осколками.