Yesterday

Игровой ИИ. State Tree

В прошлый раз мы познакомились с State Tree. Сегодня посмотрим как передавать данные между разными модулями дерева.Также не забывайте подписываться на мой канал, чтобы не пропустить следующие части.

Evaluator (Вычислитель)

Пример вычислителя для поиска подходящей цели

Evaluator - это особый тип узлов в StateTree, который активен все время выполнения конкретного дерева, и который позволяет дополнять или получать данные, необходимые для работы дерева. Отличие от глобальных задач скорее архитектурное, чем в логике выполнения: вычислители отвечают за сбор и вычисление данных, а не изменение состояния агента или какие-то действия. Простым примером может служить вычислитель поиска цели, который из списка известных Акторов выбирает лучшую цель, или вычислитель, который считает расстояние от текущей цели до агента и сохраняет его в переменную.

Data Flow

Давайте разберемся, как с помощью параметров и полей можно передавать данные между разными состояниям. Начнем с полей любых задач и вычислителей: если задача  глобальна или актуальна для текущего состояния (например, эта задача существует на родительском поле), то это поле можно передать в другую задачу или вычислитель. Для этого в StateTree используется binding полей (по аналогии с полями в Animation Blueprint).

Пример Binding векторного поля к переменной Актора

Если поле определено в Blueprint, то оно по умолчанию доступно для binding. Для полей в c++ существует несколько правил: поле должно иметь макрос UPROPERTY, также у него должны быть настройки EditAnywhere и BlueprintReadWrite, и поле должно быть публичным:

//State tree expose data
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float ParticipantsCount;

Input, Output, Context

Определяя поля возможно  разместить их для StateTree. Если поле находится в категории Input, значит поле содержит входящие данные для работы вашего класса. Если поле находится в категории Output, то ваш класс запишет результат своей работы в это поле. Поле из категории Context содержит необходимый конекст для работы класса, поэтому StateTree попробует заполнить его из вашей схемы автоматически. Поля без категории расцениваются как настройки, но их все равно можно привязывать к полям и параметрам.
N.B. стоит помнить, что даже поле должно быть видимым для настройки в редакторе, чтобы была возможность его редактировать в редакторе StateTree. Blueprint поле должно быть публичное и Instance Editable, а поле из c++ должно иметь макроc EditAnywhere.

Пример разметки полей Blueprint задачи

Parameters (Параметры)

Пример глобального параметра

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

Пример параметра определенного в состоянии

Примером параметра для состояния может служить точка назначения условного состояния передвижения. Родительское состояние содержит параметр-вектор точки назначения. Первое состояние - потомок находит подходящую точку для перемещения с помощью задачи и записывает его в параметр родителя, а второе состояние - с помощью задачи передвижения перемещается в заданную точку.

Параметры также передаются в поля классов с помощью binding.

Пример Binding параметра в поле задачи

Property Function

По умолчанию невозможно использовать функции (даже BlueprintCallable) для назначения полей (binding), но существует подход, который это исправляет. Существует возможность определить класс, унаследованный от FStateTreePropertyFunctionBase.

Такие классы выполняются перевычеслением назанченного поля и позволяют поменять логику выполнения назначения, или поменять результат назанчения. Примером может служить FStateTreeGetActorLocationPropertyFunction, которая позволяет назначать в поля позицию Актора напрямую.

Пример Binding Property Function

Дебаг и Тестирование

Одной из удобных возможностей работы со StateTree является то, что почти любой узел дерева может быть выключен или переключен в фиксированный режим во время работы в редакторе. Это очень сильно облегчает разработку, настройку и тестирование деревьев.

Например любое условие может быть переключено в режим Force True или Force False.

Любая задача может быть выключена.

Любой переход тоже может быть выключен

В Unreal Engine 5.7 для дебага используется Rewinder, который записывает все происходящее с Aкторами во время PIE, но у State Tree есть свое окно дебага, которое позволяет более подробно отслеживать что происходит с деревом и, например, какие переходы выполнялись с какими условиями.

Пример дебага StateTree

В следующий раз мы попробуем собрать простой боевой ИИ, используя StateTree