Вашим играм не нужна State Machine

Вступление

Сегодня я, наконец, расскажу, почему игровой ИИ, основанный на машине состояний (State Machine или просто SM), это ленивое подобие ИИ и не должно видеть свет в любом не пет-проекте.

Меня все также зовут Миша, я программирую больше 7 лет, последний год плотно занимаюсь игровым искусственным интеллектом, реализовывал его во всех возможных вариациях. А также успел увидеть немало говнокода со стейт машинами, где его довольно просто можно было бы избежать. Также, конечно же, веду телеграм канал, где рассказываю еще больше о разработке, карьере в геймдеве и прогреваю на менторство.

Почему нельзя?

Конечно, всегда можно сказать “да вот у меня элементарнейшее поведение, там нечего выделываться с ИИшными фреймворками” или “у меня жанр Idle, там мобы просто выполняют последовательность действий”. Но давайте вспомним цикл жизни практически любой игры. Да, мы делаем сначала core-фичи, только основной набор действий для мобов. Но как долго это длится? Как долго наши мобы из айдлеров не начинают менять поведение на ходу. Например, когда не могут что-то выполнить или когда просто хотят порадоваться просмотренной рекламке?

В такие моменты мы и понимаем, насколько были ленивы, выбрав машину состояний. Ведь у нее квадратичная сложность увеличения количества поведений. И еще все вместе вспоминем, что граф для состояний в наших стейт машинах ориентированный и умножаем количество на 2. То есть, 2 состояния - 2 стрелочки между ними, 5 состояний - 20 стрелочек, 12 → 132 и так далее.

Остаемся в зоне комфорта

Конечно, все мы следуем великой мантре “работает - не трожь!”. Поэтому как бы сильно ни стреляла нам по ногам эта стейт машина, мы остаемся с ней до победного (иногда) конца.

Второй стадией обычно идет “принятие” или как мы это называем “написание костылей”. То есть, чтобы бороться с квадратичной сложностью добавления нового поведения, программисты используют один из трех костылей или все вместе.

Рассмотрим их на примере NPC или условной RPG игры. Наш моб умеет некоторые действия типа: общаться с главным героем, ходить на работу, есть, спать, сражаться.

  • Any State. Этот синий квадратик в аниматоре знаком любому юнитисту. Проводя стрелку от него, мы показываем, что в это состояние можно перейти из любого другого. Чтобы не рисовать 20 стрелочек к 10-му состоянию, мы нарисуем 10, а одиннадцатую проведем от Any state. И все хорошо, пока мы не решим, что из какого-то состояния мы все таки не хотим переходить в это новое…
    Говоря о нашем NPC. Хотим мы ему добавить для смерти отдельное состояние (может наконец-то раскошелились на анимацию не из mixamo и хотим ею похвастаться). Мы решаем “ну умереть он может когда угодно, значит можно сделать переход из any state. Все классно, только вот NPC давал герою квест во время диалога и теперь во время болтовни он может умереть и закончить сюжетную арку на 10 часов раньше. Поэтому придется все таки провести еще 10 стрелочек.

  • Hierarchical state machine (HSM). Кому-то тоже знаком из аниматора как “вложенная стейт машина”. Если часть состояний одинаково связана с остальной частью стейт машины, то вполне резонно можно выделить ее в свою внутреннюю SM. Таким образом из 90 стрелок мы можем убрать условно половину. Но все снова идет по одному месту, когда часть таких состояний все таки должна иначе работать с внешней стейт машиной…
    Вернемся к NPC. Мы решаем, что все его мирные действия (поесть, поспать, поболтать) достойны того чтобы лежать отдельно от его боевых навыков. Убираем их отдельно и смотрим на похорошевший граф состояний… Ах да, он все еще сюжетный квест дает, но вместо диалога убежал сражаться… Тогда распаковываем обратно и рисуем обратно 40 стрелочек…

  • Слои.
    - Понимаешь, стейт машины, они как лук
    - Воняют? Доводят до слез?
    - Нет, слои. У лука есть слои и у стейт машины есть слои.
    Ставьте лайки, если узнали отсылку))
    Если какое-то действие может выполняться независимо от части остальных, то его можно вынести в отдельный слой. По своей сути, это такая же стейт машина, работающая параллельна, но в этом же внешнем контексте. Она смотрит на те же данные и меняет состояния по тем же самым триггерам, что и основная SM. Но точно также все ничего до тех пор, пока ее параллельность абсолютна. Что как правило не так…
    Геймдизайнер поиграл в скайрим и решил что наш NPC тоже может разговаривать во время некоторых действий. Например, он может делать это сидя за столом. Мы создаем отдельный слой для стейт машины, в котором есть только состояния общения. Но ведь персонаж не может уйти из-за стола, пока разговаривает. Тем более когда дает сюжетный квест. Да и разговаривать он может далеко не всегда. Что нам тогда приходится делать? Все равно опосредованно соединять наши стейт машины. Состояние общения должно точно знать что происходит в остальной логике персонажа. И та в свою очередь должна ждать, пока голова договорит. В итоге это даже хуже остальных костылей. Мы не только зазря пытаемся оптимизировать поведение, а еще и прячем зависимости состояний внутрь самого контекста игрока.

P.S. изначально примеры могли быть разнообразнее, но мне понравилось как один жалкий сюжетный квест портит абсолютно всю машину состояний. Очень разрушительный сюжет получается)

Боимся и поджимаем хвост

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

И не надо далеко ходить, чтобы понять главную альтернативу нашей стейт машине - Behavior Tree или Дерево Поведений. То, что по умолчанию есть в Unreal и то, что и было использовано в альтернативу сжигающей себя стейт машине. То, что возвело противников из серии Halo в пример остальным игроделам.

Посмотрим на “фичи”, которая она предлагает из под коробки. Заметьте, ИЗ ПОД КОРОБКИ, для этого не надо наворачивать сову на глобус как мы делали с NPC:

  • Вложенность (не просто так оно деревом называется)

  • Глобальные переходы между состояниями, как следствие из этого

  • Параллельное выполнение состояний

Конечно, нельзя не забывать и про Utility AI. Он больше подойдет в случае когда состояний много и нам не хочется выстраивать связи между ними (вспомните Sims). Хотя выбор между подходами для написания ИИ посвящена целая отдельная статья.

Итог

Не бойтесь выходить из зоны комфорта и искать новые решения для стандартных проблем! И НИКОГДА НЕ ИСПОЛЬЗУЙТЕ STATE MACHINE ДЛЯ ИГРОВОГО ИИ!!! И конечно же подписывайтесь на мой канал и обращайтесь за менторством, если до сих пор не нашли работу в геймдеве) Спасибо!