Создаем персонажа, который сам решает, что ему стоит сделать с игроком.

NPC делает селфи после убийства игрока, почему нет?

Введение

В этой статье я познакомлю вас с Goal-Oriented Action Planning, а также каким образом возможно спроектировать динамически изменяющиеся игровые миры на основе искусственного интеллекта.

Подобные методики редко применяются в СНГ, но более популярны в зарубежных студиях (чаще всего уровня ААА), где есть отдельный департамент разработки ИИ и вместо готовых ассетов проектируются интереснейшие поведения. Но чем плохо начать сейчас внедрять в наш геймдев эти техники и наконец сделать достойную триплэй игру? (̶К̶р̶о̶м̶е̶ ̶м̶и̶л̶л̶и̶о̶н̶о̶в̶ ̶д̶о̶л̶л̶а̶р̶о̶в̶ ̶н̶а̶ ̶р̶а̶з̶р̶а̶б̶о̶т̶к̶у̶)

Для понимания этой статьи достаточно знаний любого языка программирования. В дальнейшем конкретные примеры будут написаны на C# (что позволит использовать их на Unity, который чаще используется в СНГ), ссылки на реализации С++ в конце этой статьи из примера Raven Bots по книге Programming Game AI by Example.

Основные понятия искусственного интеллекта, а также как он работает в этой статье не будут затронуты (подобных статей уже много на хабр), я расскажу о расширенном варианте деревьев поведений. Если появится больше желающих, в будущих статьях расскажу более подробно о фундаментальном дизайне ИИ в играх.

Disclaimer: большинство понятий авторские, описание архитектуры и разбор механик делаю основываясь на свой опыт и исследования.

Что такое GOAP и для чего он нужен?

GOAP, или Goal-Oriented Action Planning, является методикой проектирования ИИ, при которой цепочка поведений агентов (NPC, абстрактных сущностей, "живых" препятствий) выбирается в реальном времени автоматически для удовлетворения цели. Таким образом, добавляется больше вариативности действий и непредсказуемости.

Примеры топ игр применяющих эту методику: F.E.A.R., Tomb Raider, Halo 2, Just Cause.

Стоит помнить о том, что использование определенной методики проектирования ИИ не является панацеей, также как и с использованием паттернов проектирования, необходимо использовать те методики, которые подходят для конкретного случая и реализовывать их в соответствии с поставленной задачей.

Не забывайте, что к любому сложному ИИ обязательно нужно закладывать инструменты отладки

Вариантов реализаций GOAP существует множество, в зависимости от целей игры, также как и вполне приемлемо использование нескольких методик в рамках одного проекта (нет необходимости создавать сложный ИИ для пролетающих птиц, но банально реагировать на выстрелы для ощущения присутствия они могут при помощи FSM (finite state machine) с парой нужных состояний).

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

GOB, или Goal-Oriented Behavior, является методикой проектирования ИИ, при которой поведение агента выбирается в реальном времени автоматически для удовлетворения цели. Основным отличием от GOAP и есть отсутствие планирования.

The Sims это отличный пример игры без планирования цепочки действий, потребности каждого персонажа удовлетворяются из общего набора. Действия могут быть абсолютно разными, начиная от сидения на диване, заканчивая общением с другим персонажем. В этой игре их цель это удовлетворение потребностей, что в свобю очередь требует поиска возможного действия. Для избавления от усталости нужно выполнить действие сна, поесть - если голоден, обняться - если нужна любовь.

В случае с деревом поведений (behavior tree), нам бы пришлось продумать все возможные вариации входа в условие и поведение, постоянно повторяя одни и те же действия между ветвями. GOB позволяет избавиться от огромного дерева, оставляя перед нами только список возможных событий и триггеров для их срабатывания, а использование GOAP избавляет нас от бесконечных соединений листьев в запутанном дереве.

Деревья поведений не всегда эффективны. Плохое дерево приводит к сложности тестирования и огромному количеству вытекающих проблем в будущем

Проектирование

Проектирование архитектуры перед написанием кода это одна из ключевых, но не единственных этапов дизайна ИИ, с которыми придется столкнуться разработчику искусственного интеллекта. Хотя идеи взаимодействия игровых персонажей, их логические связи и характеристики обычно определяет игровой или нарративный дизайнер. Разработчик ИИ может проектировать задачи выполняемые NPC на свое усмотрение, самое главное соблюдать итоговую цель и как говорится, игра должна оставаться "smooth and fun".

Относительно несложная схема GOAP. Имеется набор действий (например AssembleHousing), набор целей (например Key_AssembleHousing) и их значения по цели (Goal Value) + значения текущего WorldState (Current Value). Вся схема это план достижения 5 целей

Для объяснения процесса проектирования ИИ и оформления своего GOAP, разберем персонажей Hitman с точки зрения геймдизайна. Можем заметить, что они поделены на несколько типов, типы в свою очередь собираются в две группы - гражданские и сотрудники службы безопасности. Также есть отдельная группа - толпа (crowd).

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

Для оптимизации толпу можно проектировать в виде группового взаимодействия и обычно в ней присутствует лидер. Лидер группы выступает за определение, что вся группа должна сейчас делать, если необходимо бежать - лидер группы побежит и вся группа за ним (возможно добавление круга отслеживания, чтобы они бежали рядом, больше о вариантах перемещения персонажей читайте в оригинальной статье Крэга Рейнолда - Steering Behaviors for Autonomous Characters). Так как лидер может находиться дальше всей группы, необходимо также обеспечить возможность передачи ключевых триггеров от участников группы лидеру, например рядом был выстрел, участники группы передают лидеру сигнал о выстреле.

Гражданские - имеют увеличенное количество сценариев, при этом все равно с ограниченным количеством действий и условиями их вызова. Таким образом, даже в этом случае мы можем ограничиться обычным деревом поведений. Оптимизация здесь классическая - ограничиваем время на случайные поведения, отключаем полностью поведение если персонаж далеко от игрока, ограничиваем поведение в зависимости от расстояния главного героя (например, нам нет смысла проводить любые реакции на игрока, если игрок далеко или вне поля зрения - например, игрок в здании, а NPC на улице).

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

Толпа - чаще всего группа ИИ, которая практически не имеет методов взаимодействия и чаще всего группируется в одном месте

Важно!

Имея представление о том, какие методы мы будем использовать при создании архитектуры нашего будущего ИИ, стоит помнить, что подобные игры разрабатываются в среднем 2-3 года, а если речь идет о следующих частях, то повторное использование существующих модулей это классическая модель разработки. Поэтому проектируя подобную архитектуру, стоит закладывать в нее повторное использование, а значит, она должна создаваться по всем канонам SOLID, GRASP, DRY, KISS, чтобы масштабируя будущие игры мы не прострелили себе ногу запутанной архитектурой. Конкретная реализация уже зависит от языка, движка, сервер/клиента и т.д.

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

Классический GOAP

В классической модели GOAP мы будем использовать заранее продуманные цепочки задач для различных действий. Для ее реализации возьмем несколько основных классов.

  • Atomic Goal - базовые цели, для достижения которых можно выполнить 1 действие;

  • Composite Goal - составные цели, требующие плана и последовательности выполнения atomic или других composite целей;

  • Goal Evaluator - то, что нам позволит понять, насколько цель в данный момент выполнима и нужно ли ее выполнять;

  • ThinkGoal - дополнительная реализация Composite Goal, "мозг" будущего NPC.

Диаграмма архитектуры целей, оперируем двумя видами целей (Composite и Atomic), от них будем создавать возможности NPC

В этой диаграмме все просто, имеем абстрактный класс цели, от него два абстрактных класса Atomic и CompositeGoal. На что стоит обратить внимание - Subgoals список, который есть у CompositeGoal, по сути составная цель это короткий план, он может содержать как другие составные цели, так и набор простых целей. При этом каждая цель будет иметь свою вероятность активации.

Помните первое фото про селфи после убийства игрока? Составной целью здесь будет AttackTargetGoal, а последней целью внутри нее будет простая TakeSelfieGoal со случайным шансом возникновения и обязательно проверкой на только что убитого противника. Возможности ваши безграничны, добавьте эвристики сложности сражения и чем оно было сложнее, тем более вероятно срабатывание TakeSelfie. Дальше дело техники: немного магии поворота в сторону от противника, включаем анимацию или IK с поднятием головы и руки с телефоном, делаем фоточку, done. В следующих частях я разберу эту реализацию подробнее.

А сейчас разберем подробнее GoalEvaluator:

GoalEvaluator - главный оценочный класс, на любую вызываемую цель необходимо написать свой класс оценки, который будет оценивать вероятность базируясь на CalculateDesirability

Goal Evaluator - содержит всего два метода: Calculate Desirability с расчетом шанса установки цели и SetGoal, которое будет отстраивать нашу цель в базовой ThinkGoal.

ThinkGoal - составная цель, она будет базой для построения плана, именно в нее мы будем добавлять цепочку целей.

Подобная реализация позволяет нам создавать заранее продуманную игру. На примере Hitman мы можем достаточно просто спроектировать логику работников службы безопасности, список Composite Goal может включать в этом случае атаку противника, блуждание, пополнение здоровья, движение к цели или за лидером (в этом случае поведение будет у каждого свое, но если лидер куда-то отправился, его подопечные будут идти за ним и прикрывать).

Разберем пример такой Composite Goal как атака противника.

Начнем с реализации класса оценки вероятности AttackTargetGoalEvaluator. Псевдо-код может выглядить примерно таким образом:

double CalculateDesirability(NPC bot):
  desirability = 0
  // Шанс атаки отсутствует, если цель вне зоны видимости
  if (!bot.GetTarget().IsTargetPresent()) return desirability;

	desirability = NPCFeatures.Health(bot) * 
    NPCFeatures.TotalWeaponStrength(bot)
  
	return desirability

Шанс атаки и преследования цели возрастает, если цель находится в зоне видимости, у NPC достаточно здоровья (процент здоровья берем в нормализации от 0 до 1) и сила оружия также имеет высокий процент (высчитывать его можем в зависимости от ударной силы оружия и количества патронов в магазине).

Таким образом, в ситуации, когда у NPC патронов и здоровья остается мало, шанс вызова цели преследования противника уменьшается до тех пор, пока не будет вызваны другие более приоритетные цели. Например поиск укрытия и залечивания себя может стать более приоритетным, когда здоровья становится слишком мало и рядом есть стена, куда можно спрятаться. Это не будет частью плана, а будет спонтанной сменой решения. Чтобы это стало частью плана, нам придется учесть, что поиск укрытия станет частью составной цели AttackTargetGoal, персонаж будет всегда проверять возможность отойти в укрытие, как часть его механизма атаки.

Сам AttackTargetGoal будет состоять из метода Activate и Process.

void Activate():
	SetActiveIfNot()
  RemoveAllSubgoals()
  // Если цель не найдена, завершаем активацию
  if (not Owner.GetTarget().IsTargetPresent())
    CompleteGoal()
    return
  if (Owner.GetTarget().IsTargetShootable())
    // Оцениваем насколько мы можем добавить подцели стрейфа или укрытия
    if (Owner.CanStepLeft() || Owner.CanStepRight())
      AddSubgoal(new DodgeSideToSide(Owner))
    if (Owner.CanCover())
      AddSubgoal(new TryToCover(Owner))
    AddSubgoal(new SeekToPositionGoal(Owner, Owner.GetTarget().Pos())
  else // Преследуем противника, если он не в поле атаки
    AddSubgoal(new HuntTarget(Owner))

int Process():
	ActivateIfNot()
  Status = ProcessSubgoals()
  InactiveIfFailed()
  return Status

Так мы определим простую логику нашей связи целей. Цель атаки будет преследовать противника пока он не попадет в зону атаки (IsTargetShootable), как только он попадет в эту зону, мы сможем использовать стратегическую атаку в виде стрейфа или укрытия вместе с SeekToPosition, которая будет постепенно приближать нас к цели если она убегает.

Можно заметить, что атаки здесь нет, это не случайно, в классической модели дизайна ИИ любые поведения оружия отделяются от самого взаимодействия. Автоприцеливание, отдача, перезарядка и смена оружия - совсем другая история и обычно не используются как часть плана.

Вывод

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

В следующей главе я воспользуюсь движком Unity 3d и покажу конкретно на примерах с использованием языка C# как можно легко реализовать базовый GOAP с которым игра мгновенно преобразится.

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

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

Вопросы приветствуются, пожелания для следующих статей тоже. Надеюсь используете в вашей жизни GOAP, а не GOB. Всем piece!

Дополнительные материалы

  1. stannot.es - мой блог о разработке игр, торговых ботов и размышления о будущем;

  2. https://github.com/xKr0/Raven - C++ пример игры Raven Bots, использует рассмотренный GOAP;

  3. https://www.amazon.com/Programming-Example-Wordware-Developers-Library/dp/1556220782 - Game Programming AI by Example, must have для разработчиков ИИ. (В РФ не нашел, заказывал через Amazon);

  4. https://www.amazon.com/AI-Games-Third-Ian-Millington-ebook-dp-B07PYGNV64/dp/B07PYGNV64 - лучшие техники игрового ИИ, включая более хитрую систему GOAP с динамически меняющимися характеристиками.