Игровой ИИ. Behavior Tree.
Продолжаю мой цикл про игровой ИИ. В предыдущей части мы разобрались с Blackboard, сервисами и декораторами. В этой части коснемся вложенных деревьев и попробуем собрать наброски простого ИИ. Также не забывайте подписываться на мой канал, чтобы не пропустить следующие части.
Вложенные деревья
Очень часто в играх разные ИИ выполняют схожие действия, но в разных условиях и по разным причинам. Кроме того, очень часто для разных агентов высокоуровневое поведение может быть одинаковым. Например, любой противник в игре может выполнять мирное поведение, а когда заметит противника - переходить в бой, при этом само мирное поведение и боевое поведение может кардинально отличаться. Поэтому возникает желание выделить общие логики и поведения в одно место и переиспользовать их для разных агентов. Если для конечного автомата для достижения этой цели использовалась Иерархическая модель, то для дерева поведения используется аналогичный подход вложенных деревьев.
Конечный узел действия может запускать другое дерево поведения. Такой подход открывает множество возможностей для динамической настройки поведений и уменьшения повторяющегося кода и настроек. Обход дерева при посещении узла с вложенным деревом будет поставлен на паузу, после чего запускается обход вложенного дерева. Когда этот обход получит какой либо статус состояния ( это может быть любой статус, соответствующий конечному узлу действий: Running, Fail, Error и т.д.), этот статус будет передан в узел основного дерева и обход дерева продолжится в соответствии со статусом.
Статические Деревья
Простейший подход будет всегда запускать конкретное дерево, заданное через настройки узла. Такой подход отлично работает, если поведение одинаковое среди нескольких агентов. Примером таких деревьев может служить дерево, которое описывает поведение при смерти персонажа (если такое поведение присутствует и контролируется из ИИ), поведение перезарядки оружия или поведение поиска игрока.
Основные деревья агентов могут отличаться, и условия запуска статических вложенных деревьев могут отличаться, но при этом само поведение будет одинаковое.
Такой подход решает проблему повторяющихся настроек и кода для низкоуровневой части поведения, когда мы описываем конкретные действия, которые совершает противник. При этом стоит помнить, что все еще возможно менять параметры поведения, главное чтобы общая структура поведения была одинаковая. Например, если наше поведение - это набор передвижений с паузами, то точки и передвижения и паузы могут быть переданы (или логика из выбора) в дерево через BlackBoard из основного дерева. Это позволит иметь разные визуально и геймплейно поведения, используя одно и то же дерево с настройками
Динамические деревья
Второй способ использовать вложенные деревья - это проставлять дерево в узел во время выполнения ИИ. Вариаций того как это делать может быть очень много. Например:
- набор тегов и соответствующих им деревьев (такой подход представлен в UE5)
- отдельный компонент, в полях которого хранятся деревья для соответствующих ситуаций.
- локальный сервис или узел, который выбирает дерево из списка доступных.
Выбор конкретного подхода зависит от целей и требований к деревьям.
Один из частых подходов - это реализовать через отдельные деревья поведение в разных игровых ситуациях (например во время боя, во время патруля и во время поиска игрока). Причем, для каждого агента конкретное дерево под игровую ситуацию будет свое, особенное, соответствующее дизайну агента. Затем создается общее для всех агентов дерево для переключения между игровыми ситуациями на основе общих для всех агентов правил.
Blackboard и вложенные деревья.
Состояние Blackboard и подход к управлению памятью агентов может отличатся от имплементации дерева поведения и от имплементации конкретного узла вложенного дерева.
Например, в UE5 все вложенные деревья используют общий Blackboard с точностью до наследования: вложенное дерево может использовать либо тот же Blackboard, что основное дерево, либо любого предка Blackboard основного дерева.
Существуют имплементации, когда у каждого дерева свой локальный Blackboard, а данные из основного Blackboard переносятся, если необходимо, в Blackboard вложенного. При этом можно в дополнение создать общий blackboard для всего агента.
Тестовый ИИ.
После того как мы познакомились с большей частью инструментов, доступных при использовании дерева поведения, давайте попробуем собрать простой ИИ на UE5.
Мы будем использовать тестовый ИИ, описанный во второй части цикла про ИИ (https://teletype.in/@jazzyjohn/azNSiLQyzHX#hnTQ). Мы будем использовать UE 5.4 шаблон ThirdPerson на Blueprint, с плагином GAS (чтобы быстро проигрывать действия). Я не буду останавливаться на настройках GAS и персонажей, ибо в этом цикле хочу больше остановиться на ИИ составляющей. Если кому-то интересно почитать про настройку GAS вот отличный гайд (https://github.com/tranek/GASDocumentation)
Подготовка.
Для того, чтобы начать собирать ИИ, нужно завести персонажа. Мы будем использовать копию шаблонного персонажа BP_ThirdPersonCharacter, из которого удалим всю логику в Blueprint, и назовем его BP_AIControlledCharacter. Создаем новый контроллер, который наследуем от AIСontroller и назначаем новый контроллер нашему персонажу.
Поскольку нашим агентам надо будет двигаться, нам необходимо сгенерировать Navmesh. Для этого добавляем на карту Nav Mesh Bounds Volume. Растягиваем границы, чтобы зона покрывала весь уровень. Нажимаем P на клавиатуре или включаем отображение навигации в настройках рендера, чтобы убедится, что навмеш сгенерировался.
Дерево поведения
Теперь создадим необходимые ассеты для нашего ИИ. Для начала создаем Blackboard и называем его BB_TestCharacter. По умолчанию в UE5 в Blackboard сразу же существует ключ Self, с типом Actor, в котором будет записана пешка агента. Для начала добавим сразу дополнительный ключ TargetLocation с типом Vector.
После этого создаем наше корневое дерево BT_TestCharacter. Unreal по умолчанию проставит в дерево Blackboard. Поскольку в нашем проекте он один, то это будет наш blackboard. Если же в дальнейшем надо будет поменять blackboard, для этого надо будет выбрать корневой узел дерева.
Теперь откроем наш AIController и, перегрузив событие OnPossess, запустим наше дерево поведения.
Простое мирное поведение.
Давайте для начала сделаем простое мирное поведение. В нашем дизайне у агента может быть два простых мирных поведения: Idle или Wander. Idle поведение решается простым узлом Wait. Для Wander мы создадим последовательность действий.
Первым узлом мы выполняем Run EQS Query. Это запрос к EQS, который запишет случайную точку из результата в BlackBoard. Про EQS мы поговорим подробнее позже сейчас достаточно знать что это система которая работает с окружением и позволяет генерировать пригодные для навигации точки. (Вот документация от Epic Games https://dev.epicgames.com/documentation/en-us/unreal-engine/environment-query-system-overview-in-unreal-engine)
После того как запрос к EQS системе отработал, мы запускаем навигацию к точке с помощью узла MoveTo
Выбор между двумя поведениями выполним с помощью Selector. Но в таком подходе наш агент всегда будет выполнять самое левое поведение. Давайте добавим декоратор, который случайным образом разрешает поведение. Для этого создадим наследника от UBTDecorator_BlueprintBase и сделаем простую логику случайного числа, с шансом для настройки.
Теперь соберем готовое дерево.
В следующий раз мы соберем боевое поведение для нашего тестового ИИ и разберемся как передавать данные в дерево поведения.