Программирование AI врагов: системы восприятия, поведения и навигации

Наша компания по разработке видеоигр ведет независимые проекты, совместно с клиентом создает игры и оказывает дополнительные операционные услуги. Опыт нашей команды позволяет нам охватить все игровые платформы и разработать потрясающий продукт, соответствующий видению клиента и предпочтениям игроков.

От иммерсивных приложений до игровых миров и 3D-сцен

Наша выделенная команда для VR/AR/MR-разработки, Unity-продакшна и 3D-моделирования и анимации с собственными кейсами и презентациями.

Посетить персонализированный сайт
Показано 1 из 1Все 242 услуг
Программирование AI врагов: системы восприятия, поведения и навигации
Сложный
от 1 недели до 1 месяца
Часто задаваемые вопросы

Наши компетенции

Какие этапы разработки игры?

Последние работы

  • image_games_mortal_motors_495_0.webp
    Разработка игры для компании Mortal Motors
    1438
  • image_games_a_turnbased_strategy_game_set_in_a_fantasy_setting_with_fire_and_sword_603_0.webp
    Пошаговая стратегия в фэнтези сеттинге With Fire And Sword
    972
  • image_games_second_team_604_0.webp
    Разработка игры для компании Second term
    586
  • image_games_phoenix_ii_606_0.webp
    3D-анимация — тизер для игры phoenix 2.
    651
  • image_training-quizzes_kids_shopping_quiz_614_0.webp
    Обучающая викторина для детей «Покупки в магазине»
    13

Ошибка в системе восприятия врага — враги видят сквозь стены из-за отсутствия проверки Linecast. Или пустой NavMeshAgent, застрявший на стыке сцен. Такие баги убивают погружение и увеличивают время отладки на недели. Мы строим AI врагов на трёх слоях: сенсоры, принятие решений (Behaviour Tree или FSM) и память. Это исключает предсказуемость и даёт реалистичное поведение. Наша команда реализовала AI для 20+ проектов на Unity и Unreal, включая мобильные и ПК. Закажите разработку AI врагов — получите консультацию инженера.

Архитектура восприятия врага

Field of View (FOV) — стандартный метод: проверяем расстояние (до 20 единиц для среднего врага), угол (типично 90–120 градусов), и Linecast для прямой видимости. Если пропустить Linecast, враг будет реагировать на игрока за препятствием — баг, который часто встречается в инди-проектах.

Реализация Field of View

Field of View — конусная зона проверки. Не Physics.OverlapSphere сам по себе: сначала расстояние (Vector3.Distance < detectionRange), потом угол (Vector3.Angle(forward, dirToPlayer) < fovAngle / 2), потом Physics.Linecast для проверки линии видимости без препятствий. Без последнего враги видят сквозь стены.

Hearing — радиус без проверки угла, но с фильтром по типу звука. Шаги на металле слышны на 15 единиц, на мягком полу — на 5. Реализуется через AIPerceptionEvent публикуемый NoiseEmitter компонентами на движущихся объектах.

Last Known Position — критически важная концепция. Когда игрок исчезает из поля зрения, враг не «забывает» мгновенно. Хранится Vector3 lastKnownPosition и float lastSeenTime. В состоянии Investigation враг идёт к LKP, осматривается, только после таймаута (например, 5 секунд) переходит в Patrol.

Реализация пошагово

  1. Создайте компонент AIPerception на враге. Добавьте публичные поля detectionRange, fovAngle, hearingRadius.
  2. В Update() вызывайте CheckVision() и CheckHearing().
  3. Для зрения: Vector3.Distance до игрока, затем Vector3.Angle для угла, затем Physics.Linecast от глаз врага до игрока.
  4. Для слуха: подпишитесь на событие AIPerceptionEvent от NoiseEmitter. Используйте Physics.OverlapSphere с фильтром по слою шумов.
  5. Обновляйте lastKnownPosition при обнаружении. Если игрок потерян, запомните время.
  6. Для группового алертинга: при обнаружении вызовите Physics.OverlapSphere на alertRadius (например, 30 единиц), найдите всех врагов с компонентом AIPerception и вызовите у них ReceiveAlert().

Behaviour Tree vs Finite State Machine: что выбрать?

FSM (Finite State Machine) — для простых врагов с 3–5 состояниями. Реализуется как enum EnemyState + switch в Update() или паттерн State с классами. Быстро пишется, легко дебажится. Проблема: при добавлении новых состояний количество переходов растёт квадратично и FSM превращается в «спагетти» уже при 8–10 состояниях.

Behaviour Tree (BT) — для сложных врагов. Behaviour Tree позволяет гибко моделировать поведение в 2–3 раза быстрее, чем FSM, при масштабировании до 10+ состояний. Дерево из Selector, Sequence и Leaf нод. Selector выполняет детей слева направо, останавливается на первом успешном. Sequence выполняет всех детей, останавливается на первом неуспешном. Leaf-ноды — атомарные действия (MoveToTarget, AttackPlayer, PlayAnimation) и условия (IsPlayerVisible, IsHealthLow).

Пример дерева для патрульного врага:

Selector
├── Sequence (атака)
│   ├── IsPlayerInAttackRange
│   └── AttackAction
├── Sequence (преследование)
│   ├── IsPlayerVisible
│   └── MoveToPlayerAction
├── Sequence (расследование)
│   ├── HasLastKnownPosition
│   └── MoveToLKPAction
└── PatrolAction

Популярные реализации BT для Unity: NodeCanvas, Behavior Designer, Unity Muse Behavior (официальный пакет). Кастомная реализация оправдана только для специфических нужд — готовые инструменты экономят недели.

Характеристика FSM Behaviour Tree
Сложность расширения Квадратичная Линейная
Визуализация Код, диаграммы Визуальный редактор (обычно)
Повторное использование Низкое Высокое (поддеревья)
Подходит для 3–5 состояний 10+ состояний

NavMesh: навигация и типичные проблемы

NavMeshAgent — компонент Unity для автоматической навигации по запечённому NavMesh. Базовое использование: agent.SetDestination(target). Но есть нюансы.

NavMeshAgent застревает на стыке двух NavMeshSurface — классическая проблема при аддитивной загрузке сцен. Каждая сцена имеет свою поверхность, соединения через NavMeshLink нужно настраивать явно. Компонент NavMeshSurface из пакета AI Navigation заменяет старый baked NavMesh и поддерживает рантайм-обновление для динамических препятствий через NavMeshObstacle. Подробнее в официальной документации Unity AI Navigation.

SetDestination каждый кадр — лишняя нагрузка. Пересчёт пути занимает несколько миллисекунд. Рекомендуется обновлять destination не чаще чем раз в 0.1–0.2 секунды через InvokeRepeating или проверку дистанции изменения позиции цели: если цель сдвинулась менее чем на 0.5f — пересчёт не нужен.

Stopping distance и arrive behavior. agent.stoppingDistance определяет дистанцию до цели, на которой агент останавливается. Для атакующего врага это attackRange - 0.5f. При изменении состояния (от Patrol к Chase) нужно менять stoppingDistance и speed — разные состояния требуют разных параметров агента.

Почему отладка AI через Gizmos критична?

AI отлаживается через Gizmos: нарисовать FOV-конус, LKP-точку, текущий путь агента, активное состояние над головой врага. Без визуализации в редакторе разобраться в поведении AI в рантайме практически невозможно.

Профилирование: NavMesh.pathfindingTimeSlice (время на путь за кадр), количество активных агентов. На мобильных платформах более 20 активных NavMeshAgent одновременно начинают заметно нагружать CPU. Решение — LOD для AI: на расстоянии враги переключаются на упрощённое поведение без пересчёта пути.

Память AI и групповое поведение

Простая память врага: AIMemory компонент с List<MemoryEntry> где каждый entry хранит position, type (player, sound, corpse), time. Старые записи удаляются по таймауту (например, 10 записей, каждая живёт 30 секунд). При принятии решений BT или FSM запрашивают память через GetMostRecentEntry(MemoryType.Player).

Алертинг группы — когда один враг обнаруживает игрока, он должен оповестить соседей. Реализуется через Physics.OverlapSphere на радиус alertRadius (30 единиц) с фильтром по тегу Enemy, вызов enemy.GetComponent<AIPerception>().ReceiveAlert(lastKnownPosition). Это децентрализованное решение не требует менеджера.

Flanking и coordination — для тактических AI. Одна из техник: NavMeshAgent.SamplePathPosition() используется для нахождения позиций на фланге игрока, враги распределяются по этим позициям через менеджер группы. Детали реализации зависят от жанра.

Грамотная реализация AI врагов сокращает время на отладку на 30–50%, что экономит бюджет тимлида в пересчёте на человеко-часы. Качественный AI позволяет сэкономить до $500 на ранней стадии разработки за счёт снижения числа итераций тестирования.

Что входит в работу?

  • Анализ геймплейных требований и проектирование архитектуры AI
  • Реализация систем восприятия (FOV, слух, LKP)
  • Разработка поведенческих деревьев или конечных автоматов
  • Настройка навигации (NavMesh, NavMeshLink, оптимизация)
  • Интеграция памяти, алертинга и группового поведения
  • Отладка через Gizmos, профилирование и оптимизация производительности
  • Документация и обучение команды
  • Сопровождение после деплоя

Ориентировочные сроки

Сложность AI Состав Срок
Простой FSM Patrol, Chase, Attack, 3 состояния 3–5 дней
Средний Perception, LKP, Investigation, Flee 1–2 недели
Полный BT NodeCanvas/Behavior Designer, группы, координация 3–6 недель
Сложный тактический Flanking, cover system, squad AI 2–3 месяца

Мы — команда с 10+ годами опыта в геймдеве, реализовали AI для 20+ проектов на Unity и Unreal. Гарантируем работоспособность и поддержим после сдачи. Свяжитесь с нами для консультации — мы подберём оптимальное решение для вашего проекта.

Программирование игр: геймплей и системная логика

К нам приходит проект с хаотичной архитектурой. Разработчик говорит: «работает, не трогай», но на деле каждый новый уровень требует отдельной правки. Типичная картина: контроллер персонажа на 2000 строк, где физика, анимация, UI и звук перемешаны в одном MonoBehaviour. Сохранение через PlayerPrefs с ключами типа "player_hp_current_value_int". Это не гипотетика — это результат отсутствия архитектурного решения в начале. Мы занимаемся геймплейным программированием более 7 лет и реализовали свыше 15 проектов — от мобильных гиперказуалок до PC-шутеров. Гарантируем, что после нашего вмешательства проект перестаёт быть «чёрным ящиком». Закажите бесплатный аудит кода — оценим состояние за 2 дня.

Мы берём такой код, проводим аудит и реорганизуем его в модульную систему. Геймплейное программирование — сердце игры. Здесь закладывается ощущение от управления, интеллект противников, честная физика и надёжная система прогресса. Сделано плохо — никакой арт не спасёт.

В материале разберём, как мы строим контроллеры, физику, ИИ и систему сохранений, чтобы игра работала предсказуемо и без багов. Если ваш проект уже страдает от хаотичной архитектуры — получите консультацию инженера до старта работ.

Контроллер персонажа

Почему контроллер персонажа задаёт тон всей игре? Первое, с чем сталкивается игрок — управление. Задержки, скользкое движение, «залипание» на препятствиях — всё это считывается мгновенно и портит впечатление раньше, чем игрок успевает увидеть геймплей. Базовый выбор сводится к двум вариантам: CharacterController или Rigidbody.

CharacterController — встроенный компонент Unity, специализированный для персонажей. Игнорирует физический движок для движения, но корректно обрабатывает ступени, наклонные поверхности и препятствия. Рекомендован для экшн-игр, платформеров, шутеров от первого лица — там, где нужен точный предсказуемый отклик.

Rigidbody — физический объект. Необходим, когда персонаж должен взаимодействовать с физическими объектами: толкать ящики, реагировать на взрывы, быть подброшенным. Требует аккуратной работы через FixedUpdate и осторожного отключения гравитации или трения, чтобы управление не казалось «плавающим».

Для большинства 3D-проектов мы используем CharacterController с кастомным обработчиком гравитации — это даёт контроль без артефактов физического движка. Для 2D — Rigidbody2D с constraints на вращение и тщательно настроенным Collision Detection Mode: Continuous. Каждое решение принимаем под жанр и целевую платформу.

Физика и коллизии

Rigidbody и коллайдеры — источник регулярных проблем, если их не настроить правильно с самого начала. Несколько правил, которые экономят время:

  • Collision Detection: Continuous для быстрых объектов (пули, снаряды) — иначе они «пролетают» сквозь тонкую геометрию
  • Сложные меш-коллайдеры заменяем составными примитивами (Box + Capsule + Sphere) — это дешевле для физики на 70–80%
  • Слои (Physics Layers) и матрица коллизий в Physics Settings настраиваются в начале проекта — потом добавить их без рефакторинга очень болезненно
  • Все физические вычисления — в FixedUpdate, не в Update. Иначе поведение зависит от FPS

Соблюдение этих правил снижает количество багов с коллизиями на 90% уже на этапе прототипа.

Чек-лист типичных ошибок при настройке физики
  • Частицы или UI-объекты с коллайдерами — невидимые препятствия для пуль
  • Триггеры, висящие на объектах без Rigidbody — события не срабатывают
  • Скорость пули > 100 м/с без Continuous Dynamic — сквозные пролёты
  • Один коллайдер на сложную сетку вместо композитных — падение FPS на 40–50%

Как архитектура ИИ влияет на игровой опыт?

Плохой AI виден сразу: противники застревают в углах, атакуют через стены, предсказуемо патрулируют по одному маршруту. Разница между «работает» и «работает хорошо» здесь наиболее ощутима. Рассмотрим три уровня детализации.

Конечные автоматы (State Machines)

Самый распространённый подход — иерархический конечный автомат (HSM). Каждое состояние: Idle, Patrol, Chase, Attack, Dead — это класс или метод с входом, обновлением и выходом. Wikipedia: конечный автомат

public enum EnemyState { Idle, Patrol, Chase, Attack, Dead }

private void UpdateStateMachine() {
    switch (_currentState) {
        case EnemyState.Patrol:
            UpdatePatrol();
            if (CanSeePlayer()) TransitionTo(EnemyState.Chase);
            break;
        case EnemyState.Chase:
            _navMeshAgent.SetDestination(_player.position);
            if (InAttackRange()) TransitionTo(EnemyState.Attack);
            if (!CanSeePlayer() && _lostSightTimer > 5f) TransitionTo(EnemyState.Patrol);
            break;
        // ...
    }
}

State Machine хорошо работает для противников с небольшим числом состояний (5–8). При росте сложности — взрывной рост переходов между состояниями, код становится трудно читать и тестировать.

Behaviour Trees

Behaviour Tree — следующий уровень. Дерево поведения описывает логику агента через иерархию задач: Sequence, Selector, Decorator, Leaf. Wikipedia: дерево поведения

Преимущество перед State Machine: каждый узел атомарен и переиспользуем. Узел CheckLineOfSight написан один раз и используется в десяти деревьях. Добавить новое поведение — значит добавить ветку в дерево, не рефакторить существующую логику.

В Unity BT реализуется через ассеты (NodeCanvas, Behaviour Designer) или кастомную реализацию. Для крупных проектов с несколькими типами врагов это окупается уже на этапе второго типа противника. Пример структуры дерева для патрульного противника:

Root
└── Selector
    ├── Sequence (Combat)
    │   ├── IsPlayerVisible
    │   ├── IsPlayerInRange
    │   └── AttackPlayer
    ├── Sequence (Alert)
    │   ├── HeardSound
    │   └── InvestigatePosition
    └── Sequence (Patrol)
        ├── HasPatrolRoute
        └── FollowPatrolRoute

GOAP — когда BT недостаточно

Goal-Oriented Action Planning — подход для действительно сложного AI, где агент должен планировать последовательность действий для достижения цели с учётом текущего состояния мира. Классический пример — противник, которому нужно «убить игрока». Если у него нет оружия, он ищет оружие. Если нет боеприпасов, он ищет патроны. Если игрок укрылся, он ищет обходной маршрут. GOAP позволяет задать эти действия и их предусловия/постусловия, а планировщик строит цепочку автоматически.

GOAP значительно сложнее в реализации, чем BT, и оправдан не всегда. Для платформеров и казуальных игр это избыточно. Для тактических игр, симуляторов выживания, стелс-экшн — может быть правильным выбором.

NavMeshAgent и навигация

NavMeshAgent — стандартный инструмент для навигации в Unity. Работает корректно при правильной настройке NavMesh и агентов:

  • Agent Radius и Agent Height должны точно соответствовать коллайдеру персонажа
  • Stopping Distance нужно настраивать под дальность атаки каждого типа врага
  • NavMesh Obstacle с Carve: true для динамических препятствий (падающие ящики, закрывающиеся двери) — иначе агенты будут пытаться пройти сквозь них
  • Для больших открытых миров — NavMesh Links для соединения отдельных сегментов и Off-Mesh Links для прыжков и спусков
Критерий State Machine Behaviour Tree
Переиспользование логики Низкое (состояния привязаны к контексту) Высокое (узлы независимы)
Масштабирование Взрыв переходов при 10+ состояний Линейный рост дерева
Отладка Сложная (требуется полный стейт-трекер) Простая (видно текущий узел)
Сложность внедрения Низкая (старт за 1 день) Средняя (3–5 дней на настройку)
Рекомендуемый объём До 6 типов врагов От 6 типов врагов

Почему система сохранений требует версионирования?

Вторая область, где архитектурные решения в начале критически влияют на всё последующее. Сохранения, добавленные в конце разработки «за неделю», почти всегда ломаются при изменении структуры данных. Рассмотрим инструменты.

PlayerPrefs — когда подходит и когда нет

PlayerPrefs — это хранилище простых ключ-значение (string, int, float). Подходит строго для настроек (громкость, управление, язык). Использовать его для хранения состояния игрового мира — ошибка: нет типизации, нет версионирования, нет удобного дебага.

JSON-сериализация

Рабочий подход для большинства проектов — сериализация данных в JSON через JsonUtility (встроенный, быстрый, но ограниченный) или Newtonsoft.Json (полноценный, поддерживает словари, наследование, nullable типы). Структура системы сохранений:

[Serializable]
public class SaveData {
    public int version = 1;          // версионирование
    public PlayerSaveData player;
    public WorldSaveData world;
    public SettingsSaveData settings;
}

public class SaveSystem : MonoBehaviour {
    private const string SAVE_FILE = "/save.json";

    public void Save(SaveData data) {
        string json = JsonConvert.SerializeObject(data, Formatting.Indented);
        File.WriteAllText(Application.persistentDataPath + SAVE_FILE, json);
    }

    public SaveData Load() {
        string path = Application.persistentDataPath + SAVE_FILE;
        if (!File.Exists(path)) return new SaveData();
        string json = File.ReadAllText(path);
        return JsonConvert.DeserializeObject<SaveData>(json);
    }
}

ScriptableObject как контейнер данных

ScriptableObject — недооценённый инструмент для хранения игровых данных. Конфигурации предметов, характеристики противников, параметры уровней — всё это удобнее держать в ScriptableObject, чем в JSON или константах в коде. Для сохранений ScriptableObject используется в паттерне Runtime Set и Variable: значения хранятся в ScriptableObject, сохранение записывает только дельту относительно дефолтных значений.

Версионирование сохранений

Поле version в корне SaveData — не бюрократия, а необходимость. Когда после релиза добавляется новая механика с новыми полями, нужно корректно мигрировать старые сохранения. Миграционный метод:

private SaveData MigrateSaveData(SaveData data) {
    if (data.version < 2) {
        data.player.newField = defaultValue;
        data.version = 2;
    }
    if (data.version < 3) {
        // следующая миграция
        data.version = 3;
    }
    return data;
}

Без версионирования приходится выбирать между сломанными сохранениями у игроков или отказом от изменения структуры данных.

ScriptableObject-архитектура

Для средних и крупных проектов мы используем подход, популяризированный Ryan Hipple на GDC: ScriptableObject как шина событий и хранилище разделяемого состояния.

// Переменная-событие
[CreateAssetMenu]
public class GameEvent : ScriptableObject {
    private List<GameEventListener> _listeners = new();

    public void Raise() {
        for (int i = _listeners.Count - 1; i >= 0; i--)
            _listeners[i].OnEventRaised();
    }
}

Это позволяет системам в игре взаимодействовать без прямых ссылок друг на друга. PlayerHealth не знает о UI, UI не знает о GameManager — все они знают только о ScriptableObject-событиях. Проект становится значительно легче тестировать и расширять.

Как мы работаем: этапы и результат

Каждый проект проходит через пять этапов. Ниже — ориентировочные сроки и ключевые артефакты. Стоимость каждого этапа рассчитывается индивидуально, ориентир: один модуль (например, система сохранений) — от 20 000 до 50 000 рублей в зависимости от сложности.

Этап Длительность (рабочие дни) Результат
Аудит текущей архитектуры 1–3 Документ с проблемами и рекомендациями
Проектирование систем 2–5 Архитектурная схема, описание модулей
Реализация (итерации) от 10 Рабочий код, протестированный в связке с артом
Код-ревью и рефакторинг 2–4 Чистая кодовая база, комментарии к сложным участкам
Документация и сдача 1–2 Гайд для команды, описание настроек (Physics Layers, NavMesh и т.д.)

Сроки варьируются в зависимости от объёма legacy-кода и сложности механик. Получите консультацию инженера до старта работ — убедимся, что подход подходит именно вашему проекту.

Что входит в работу

После завершения вы получаете:

  • Архитектурную документацию игровых систем (контроллер, AI, сохранения)
  • Исходный код с комментариями, готовый к дальнейшей разработке
  • Настройки инструментов: Physics Layers, NavMesh, проект ScriptableObject‑событий
  • Код-ревью существующих модулей (если проект не с нуля)
  • Поддержку на этапе интеграции (2 недели после сдачи)

Свяжитесь с нами, чтобы обсудить детали. Закажите аудит архитектуры — это бесплатно и займёт не более 3 рабочих дней.