Траектория «Геймдев» · линия A · конспект 11 из 13

Простой ИИ на C#: машина состояний

О чём эта тема
Первый противник с поведением: камера слежения замечает игрока, охранник преследует его по навигационному мешу и успокаивается по таймеру — простой ИИ как машина состояний.
Аннотация
Конспект строит стелс-миниатюру по разбору с Хабра: игрок пробирается мимо «вражеского штаба», датчик-камера патрулирует поле, при касании поднимает тревогу, и охранник в течение пяти секунд преследует игрока. Сначала поведение описывается как машина состояний — прямое продолжение схемы из A02. Затем собирается сцена: навигационный меш и компонент NavMeshAgent, три объекта и три скрипта — Player, Guard и Looker, с полным кодом и разбором. Поведение системы можно прочувствовать в интерактивной модели: поуправлять игроком, попасться камере и убегать от охранника.
Пререквизиты
A02 — машина состояний; A07 — жизненный цикл, GetComponent; A08 — AddForce, SerializeField; A09 — коллайдеры и события столкновений.
Мотивация
Почти любой игре нужен управляемый компьютером персонаж — чаще всего враждебный игроку. В A02 машина состояний врага (патруль → атака → бегство) осталась схемой с заглушками вместо поведения. Пришло время заполнить заглушки: научить противника замечать игрока, догонять его по уровню, огибая препятствия, и прекращать погоню. Всё это делается тремя короткими скриптами и одним встроенным инструментом Unity — навигационным мешем.

1. Поведение как машина состояний

Сформулируем задачу. Игрок должен тайком пробраться мимо вражеского штаба. Поле патрулирует «камера слежения» — плоский красный диск, курсирующий вверх-вниз. Если игрок коснулся камеры — тревога: охранник преследует игрока; через пять секунд после тревоги охранник останавливается, и всё начинается заново.

Прежде чем писать код, нарисуем машину состояний охранника — так, как мы делали это в A02:

Ожидание
Guard стоит · Looker патрулирует
→ игрок коснулся камеры →
Преследование
Guard идёт к игроку · таймер 5 с
→ таймер истёк →
Ожидание

Состояний всего два, и переключать их можно самым простым способом, какой есть в Unity: включать и выключать скрипт преследования через свойство enabled. Выключенный скрипт не получает Update — охранник «не думает»; включённый — преследует. Роль «стрелок» схемы играют событие столкновения и таймер.

2. Сцена и навигационный меш

Объекты сцены (проект 3D, папки Materials и Scripts — как мы заводили в A05):

ОбъектИз чего и с чемРоль
FloorPlane, масштаб X = 7, Z = 3; свой материалпол, по которому все перемещаются
Wall × 4Plane, повёрнутые вертикально по периметрустены, чтобы игрок не свалился с края
PlayerSphere + Rigidbody + тег Playerшар игрока под управлением стрелок
GuardCube + Rigidbody + NavMeshAgentохранник-преследователь
LookerSphere, сплюснутая (масштаб 9 × 0.5 × 9), ярко-красный материал«глаза» охранника: патрулирующий датчик

Камеру ставим сверху (Y = 30, наклон X = 80°) — вид на поле сверху. Тег Player назначается в выпадающем списке Tag в верхней части инспектора — по нему код будет отличать игрока от прочих объектов.

Навигационный меш (NavMesh) — заранее просчитанная карта проходимых мест, по которой агенты умеют строить маршрут. Создаётся так: Window → Navigation; выбрать Floor и отметить флажок Navigation Static (пол — часть статичной геометрии для навигации); перейти на вкладку Bake и нажать кнопку Bake. Unity просчитает меш, и по полу можно будет «ходить» программно. Компонент NavMeshAgent на охраннике — это исполнитель: ему задаётся точка назначения, а путь до неё он строит и проходит сам.

Типичная ошибка Забыть запечь NavMesh (или изменить уровень и не перезапечь). NavMeshAgent без меша просто не двигается — без ошибок и объяснений. Если охранник стоит на месте, первым делом проверяйте Window → Navigation → Bake.

3. Три скрипта

3.1. Player: движение силами

using UnityEngine;

public class Player : MonoBehaviour
{
    private Rigidbody rb;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    void Update()
    {
        if (Input.GetKey(KeyCode.UpArrow))
            rb.AddForce(Vector3.forward * 20);
        if (Input.GetKey(KeyCode.DownArrow))
            rb.AddForce(Vector3.back * 20);
        if (Input.GetKey(KeyCode.LeftArrow))
            rb.AddForce(Vector3.left * 20);
        if (Input.GetKey(KeyCode.RightArrow))
            rb.AddForce(Vector3.right * 20);
    }
}

Знакомая по A08 схема «сила к твёрдому телу», только источник команд — не кнопки UI, а клавиатура: Input.GetKey возвращает true, пока клавиша зажата. Чтение ввода — в Update, как и положено по A07.

3.2. Guard: преследование по мешу

using UnityEngine;
using UnityEngine.AI;                       // пространство имён навигации

public class Guard : MonoBehaviour
{
    [SerializeField] private GameObject player;  // кого преследовать; назначается в инспекторе

    private NavMeshAgent navmesh;

    void Start()
    {
        navmesh = GetComponent<NavMeshAgent>();
    }

    void Update()
    {
        // каждый кадр цель маршрута — текущая позиция игрока
        navmesh.destination = player.transform.position;
    }
}

Весь «интеллект» преследования — одна строка: агенту каждое обновление задаётся точка назначения, равная позиции игрока, а маршрут и обход препятствий NavMeshAgent берёт на себя. Заметьте: скрипт всегда преследует — состояние «ожидание» получится его выключением извне. На объекте Guard этот компонент изначально выключен (снята галочка в инспекторе).

3.3. Looker: датчик, таймер и переключение состояний

using UnityEngine;

public class Looker : MonoBehaviour
{
    [SerializeField] private GameObject guard;  // охранник; назначается в инспекторе

    private float reset = 5f;                   // таймер тревоги, секунды
    private bool movingDown;

    void Update()
    {
        // патрулирование: движение вперёд-назад по оси Z
        if (movingDown == false)
            transform.position -= new Vector3(0, 0, 0.1f);
        else
            transform.position += new Vector3(0, 0, 0.1f);

        if (transform.position.z > 10)
            movingDown = false;
        else if (transform.position.z < -10)
            movingDown = true;

        // обратный отсчёт тревоги в реальном времени
        reset -= Time.deltaTime;
        if (reset < 0)
        {
            guard.GetComponent<Guard>().enabled = false;   // погоня окончена
            GetComponent<SphereCollider>().enabled = true; // датчик снова «зрячий»
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Player"))
        {
            guard.GetComponent<Guard>().enabled = true;     // тревога: включить погоню
            reset = 5f;                                     // перезапустить таймер
            GetComponent<SphereCollider>().enabled = false; // датчик «ослеп», игрок не застрянет
        }
    }
}

Здесь и происходит переключение машины состояний:

Финальная сборка: скрипт Player — на объект Player; скрипт Guard — на объект Guard, в поле player перетащить объект Player, галочку компонента снять; скрипт Looker — на объект Looker, в поле guard перетащить объект Guard. Запускаем: игрок катается стрелками, камера патрулирует; коснулись красного диска — охранник срывается в погоню и через пять секунд отстаёт.

Типичная ошибка Оставить скрипт Guard включённым. Тогда охранник преследует игрока с первого кадра, и состояние «ожидание» не существует: машина состояний должна стартовать из правильного состояния, а начальное состояние здесь — выключенный компонент.
Тренажёр · штаб под охраной

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

Состояние охранника: ожидание

Кликните по полю, чтобы клавиатура работала, — или пользуйтесь кнопками.

Контрольные вопросы

Источники

  1. Создание простого ИИ на C# в Unity // Хабр : [сайт]. — URL: https://habr.com/ru/articles/419641/ (дата обращения: 08.07.2026).
  2. Navigation and Pathfinding // Unity Documentation : [сайт]. — URL: https://docs.unity3d.com/Manual/Navigation.html (дата обращения: 08.07.2026).
  3. NavMeshAgent // Unity Scripting API : [сайт]. — URL: https://docs.unity3d.com/ScriptReference/AI.NavMeshAgent.html (дата обращения: 08.07.2026).