AI GOAP - планировщик целеориентированного ИИ

AI Goap - это реализация GOAP (Goal Oriented Action Planning) планировщика последовательности не связанных явно между собой действий для достижения определенной цели. Звучит не очень понятно, давайте разбираться.
Введение
Деревья Поведения (BehaviourTree) - это самая распространенная реализация для игрового ИИ, с которой все начинают, а часто и заканчивают, так как предоставляемого функционала для простых ботов хватает. Когда же схемы разрастаются, то возникает проблема по настройке старых и добавлению новых взаимосвязей между узлами действий - на сцену выходит GOAP.
GOAP - это подход к разработке ИИ для игровых персонажей, основанный на планировании действий с учетом целей. GOAP позволяет персонажам принимать решения и планировать свои действия на основе текущей ситуации и желаемых целей.
Основная идея GOAP заключается в том, что персонажи имеют набор целей, которые они стремятся достичь, и набор действий, которые они могут выполнить для достижения этих целей. Планировщик GOAP анализирует текущую ситуацию, оценивает доступные действия и выбирает оптимальную последовательность действий, которая приведет к достижению выбранной цели.
Преимущество GOAP заключается в гибкости и адаптивности за счет отсутствия явных связей между действиями, что позволяет достаточно просто расширять логику поведения.
Основные определения GOAP
- Условие. Флаг-признак чего-то, может быть либо в состоянии “Да”, либо “Нет”.
- Действие. Абстрактный элемент-узел плана действий.
GOAPничего не знает о том, что происходит вДействиии требует явно переключатьУсловия, на которые влияет этоДействие. - Требования. Набор
Условий, которые должны выполниться для того, чтобыДействиестало возможным к использованию. - Планировщик. Сердце
GOAP, получает список действий, стартовое состояниеУсловий, цель, состоящую из таких жеУсловий. На выход подает список действий в нужной последовательности для достижения указанной цели. Если цель недостижима - информирует об этом.
Пример
Ну и давайте рассмотрим простой пример - бот может быть “голодным”, он умеет “заказывать пиццу”, умеет “готовить еду” сам, умеет “ждать” и умеет “есть”.
“Заказывать”, “Ждать”, “Готовить” и “Есть” - это Действия.
“Голодный”, “Есть номер телефона”, “Позвонил в пиццерию”, “Жду заказ”, “Есть ингредиенты”, “Еда в наличии” - это Условия.
Переведем это в код:
Условия
1 | enum Conditions { |
Действия
1 | // Действие "Позвонить в пиццерию и сделать заказ" |
Теперь давайте попробуем построить план. Пусть бот будет голодным, у него будет номер пиццерии, задача перестать испытывать голод:
1 | List<string> result = new(); |
Цель может быть как достижима, так и нет, об этом планировщик просигнализирует, вернув флаг успеха. Если путь валидный - мы можем просмотреть шаги плана:
1 | if (valid) { |
Результат:
“Call,WaitOrder,Eat” - “Позвонить в пиццерию”, “Подождать заказа”, “Съесть”.
Так, а что если номера нет, а есть ингредиенты для еды?
1 | // Инициализация планировщика та же, отличаются только параметры у Run(). |
Результат:
“Cook,Eat” - “Приготовить еду”, “Съесть”.
Отлично. Но что будет, если у нас есть и номер пиццерии и ингредиенты?
1 | // Инициализация планировщика та же, отличаются только параметры у Run(). |
Результат:
“Cook,Eat” - “Приготовить еду”, “Съесть”.
Тот же результат, но почему? У каждого действия есть “сложность/стоимость”, которая по умолчанию равна 1.0f. Получается "Call,WaitOrder,Eat" => 1+1+1=3, а "Cook,Eat" => 1+1=2 - самый “дешевый/простой” вариант победил. Мы можем настраивать этот параметр у каждого действия отдельно, давайте сделаем сложность приготовления пищи равной 10.0f:
1 | class CookAction : IGoapAction<string> { |
Реализация действия осталась прежней, добавился метод Cost(), который возвращает новую “сложность/стоимость” действия. Запускаем тест еще раз:
Результат:
“Call,WaitOrder,Eat” - “Позвонить в пиццерию”, “Подождать заказа”, “Съесть”.
Так получилось потому, что "Call,WaitOrder,Eat" => 1+1+1=3, а "Cook,Eat" => 10+1=11 - победил заказ еды в пиццерии.
А что если у нас нет ни номера пиццерии, ни ингредиентов?
1 | var valid = planner.Run ( |
Результат:
“Нет плана” - цель недостижима.
Вот таким нехитрым способом строится план действий, который уже потом можно использовать для выполнения ботом.
Актуальные версии пакетов доступны в GitVerse.