База поведения: выполнение задач с помощью подзадач

Базисом системы поведения в Destroy All Humans 2 является иерархическая машина конечных состояний (Hierarchical Finite State Machine, HFSM), в которой текущее состояние актера определяется через несколько уровней абстракции. На каждом уровне иерархии состояние потенциально может использовать подуровни состояния, чтобы разбить задачи на более мелкие проблемы (например, «атаковать_противников» на высоком уровне абстракции использует менее абстрактную функцию «стрелять_из_оружия» на более низком уровне, чтобы выполнить часть своей задачи). Такая структура HFSM – обычный метод, используемый в игровом ИИ, чтобы формализовать поведение персонажа. Она имеет несколько явных преимуществ над плоской машиной конечных состояний (FSM). (Для текущей информации о HFSM см. Источники).

В нашей реализации каждое состояние HFSM именуется привычкой, которая является базовым архитектурным элементом системы. Все, что может делать персонаж в игре, конструируется с помощью соединения привычек различными способами, позволяемыми HFSM. Привычка может запустить другие привычки ниже ее, которые будут действовать как дочерние процессы, и каждый из них будет выполнять более мелкую (и более конкретную) часть задачи родителя. Разбив каждую задачу на более мелкие элементы, мы смогли убрать большое количество необходимой информации из элемента привычки – используя его в других привычках, перекрывая его в особых случаях, динамически изменяя структуру и так далее – и потратить больше времени на то, чтобы сделать систему интуитивной и легкой в модификации. Запуск дочернего процесса. Есть много способов разбить задачу на меньшие элементы, и правильный выбор зависит, прежде всего, от типа задачи. Требует ли задача поддерживать определенные условия, выполнять последовательные шаги, случайно выполнять действие из списка или что-то еще? В нашей реализации мы ввели несколько методов разбиения привычки на более мелкие элементы, позволив различные способы запуска дочерних привычек.

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

Когда привычка находится в режиме очереди, она не обновляется; вместо этого она ожидает, пока не решит сама, в зависимости от своих собственных установок, что может активироваться. При активации привычка в свою очередь запустит те дочерние привычки, что есть у нее. Только у активных привычек допускаются дочерние, так что привычка в очереди будет ожидать до момента, пока сможет запустить дочернюю. Как правило, только одна активная привычка может выполняться под данным родителем, что создает определенную проблему: что делать, если выполняются условия запуска нескольких привычек. Нам необходимо установить приоритет, чтобы определить, какая из подзадач более важна. При запуске дочернего процесса в приоритетном режиме мы определяем их приоритет естественным образом на основе порядка, в котором привычки добавлены к родителю. Чем раньше в списке встречается привычка, тем выше ее приоритет. (См. Isla в Источниках).
Такое решение обходит проблему, которая могла бы возникнуть, если бы мы определяли приоритет строго по номеру: «ползучие приоритеты», когда приоритеты становятся все больше и больше, пытаясь перебить остальные. Мы же локализовали определение приоритета, так что он относится лишь к маленькому набору тех привычек, что запускаются как младшие.
В примере выше мы увидели иерархию привычек и дочерних процессов, доступных для каждой, где «стрельба» активируется как дочерний процесс к «атаке», а та, в свою очередь, становится активным дочерним процессом «боя». Если привычка, которая сейчас находится в очереди («уклоняться») определит, что должна запуститься (когда NPC определит, что по нему стреляют), она прервет активную младшую привычку («атаковать») и вернет ее в очередь, что, в свою очередь, удалит все дочерние процессы атаки. Как только не останется ни одной младшей привычки, может начаться уклонение. Такой метод применения естественного приоритета хорошо срабатывает в большинстве случаев, но иногда важность задач не может быть определена простым линейным порядком. Чтобы обрабатывать случаи, когда привычка выполняет что-то важное и не должна прерываться некритичными задачами (даже если их приоритет выше), мы можем реализовать особенность, называемую «нельзя прерывать». По сути, такая особенность означает, что активная привычка может получить повышение приоритета, предотвращающее прерывание во время определенных частей выполнения.
При наличии такого повышения приоритеты могут описываться более сложным способом, чем простой линейный порядок. Например, хотя «рукопашная схватка» в списке имеет меньший приоритет, чем «уклонение», ей нужно разрешить закончить анимацию, даже если решит запуститься уклонение – и с помощью повышения приоритета на время запуска мы предотвращаем, чтобы уклонение прервало действие прямо посередине анимации. Последовательные и случайные дочерние процессы. Другие методы запуска дочерних привычек известны как последовательные и случайные. Привычки, которые запускаются последовательно, выполняются в том порядке, в котором перечислены в списке. Если первая может запуститься, она делает это до тех пор, пока не закончится, после чего запускается вторая, и так далее. Когда последняя привычка последовательности завершает работу, завершается и родительская привычка. Для группы дочерних привычек, которые запускаются в случайном порядке, выбирается для запуска только одна, а родитель будет завершен, когда она выполнится.
Неблокирующие дочерние процессы. Привычки также могут быть запущены как неблокирующие, в каком случае они могут активироваться, даже если уже есть другие активные привычки, которые запущены под родительскими. Они существуют вне списка приоритетов. Такие привычки полезны, чтобы выполнять задачи, которые работают одновременно с другими, такие как стрельба во время движения, проигрывание голоса в определенных условиях или эффекты активации и деактивации. Как правило, то, что выполняется неблокирующими привычками, не должно интерферировать с любыми младшими привычками, которые могут быть запущены, поскольку неблокирующая привычка может прерваться, только когда деактивируется ее родитель (и никогда – младшей привычкой).
Используя различные комбинации этих методов выше и ниже в дереве, мы можем формировать решения и обработку задач на многих уровнях, что было бы трудно определить единичной привычкой.

Добавить комментарий