«Кунг-Фу Круг»: система контроля атак NPC
Вот уже несколько лет я, как любитель слэшеров и битемапов, занимаюсь поиском и сбором информации по комбат-дизайну для игр этих жанров. Недавно эти поиски привели меня к одноименной главе из книги по игровому ИИ, чьим дополненным переводом и является эта статья.
Если в кратце, то эта статья о том, как сделать более упорядоченные и осмысленные close-combat сражения, которые в своем удачном исполнении будут больше походить на хорошо поставленный танцевальный номер. Ну или на жесткое кровавое месиво.
Хотя эта статья написана про экшн игру с боевкой на холодном оружии, сама система с таким же успехом будет применима к любой игре, где есть милишные враги (как минимум).
Вступление
В экшн-играх с риал-тайм боевкой необходимо найти интересный баланс между сложностью и вызовом для игрока. Зачастую в таких играх могут встречаться ситуации, в которых на игрока нападают сразу несколько противников, часто с разными типами атак. Это может быть продиктовано геймдизайном по разным причинам: начиная от необходимой продолжительности боя до требований повествования.
В таких случаях необходимо создать умную и, самое главное, контролируемую систему ротации врагов и их действий (особенно — атак). В противном случае игрок может столкнуться либо с кашей из врагов, которые ведут себя очень примитивно, либо просто с неиграбельными ситуациями типа непроходимых отрезков или софт-локов.
«Кунг-Фу Круг» — система, способная динамически менять типы атакующих врагов и их количество, а также набор их действий, на основе наличиях или отсутствия врагов и выбранном уровне сложности.
Бельгийский AI
Разработчики игры Kingdoms of Amalur: Reckoning для своей игры ввели такое понятие как «Бельгийсикй AI» — в честь бельгийских вафель, которые похожи на квадратную сетку. Этот подход позволил задать универсальные правила для дизайна любых энкаутеров, с любым набором врагов.
Высокоуровне алгоритм Бельгийского AI представляет из себя сетку (грид), которую “носит” с собой каждое существо в игре. Такая сетка есть и у NPC, но в нашем случае будем рассматривать сетку игрока, как сущность, которая интересует нас больше всего.
Сетка центрирована по игроку и включает в себя 8 слотов для врагов. Она напоминает поле боя в крестики-нолики, только игрок находится в центре.
Помимо физического расположения этих слотов, сетка хранит
две переменные:вместимость сетки и вместимость атаки.
- вместимость сетки ограничивает количество врагов, которые могут атаковать игрока одновременно;
- вместимость атаки ограничивает количество и типы атак, которые они могут использовать;
Каждому существу в игре присваивается вес сетки, который представляет из себя “стоимость” этого существа для занимания слота. Общий вес сетки существ, одновременно атакующих игрока, должен быть меньше, чем вместимость сетки игрока.
Аналогично, каждая атака имеет вес атаки, и общий вес всех атак, используемых против игрока в любой момент времени, должен быть меньше, чем вместимость атаки игрока.
Рассмотрим принцип работы этих переменных на примере: представим ситуацию, когда вражеский солдат обнаружил игрока и решает начать атаку. Перед этим солдату необходимо запросить место, с которого он сможет атаковать игрока. Сам солдат не просчитывает веса и вместимости и не общается напрямую с игроком — за все подобные расчеты и общение отвечает отдельная сущность на сцене, назовем её менеджер сцены.
Поэтому солдат сначала зарегистрирует запрос атаковать игрока у менеджера сцены и будет ждать, когда ему выделят место. При следующем тике менеджер сцены обработает запрос и сравнит вес сетки солдата с текущим весом игрока в сетке.
Для нашего примера мы предположим, что вес сетки солдата равен 4, а вместимость сетки игрока — 12. Поскольку вес сетки солдата меньше доступной вместимости сетки, менеджер сцены назначит солдата на ближайший доступный слот сетки и уменьшает доступную вместимость сетки игрока до 8. Теперь у солдата есть разрешение подойти к игроку и начать атаку.
Далее, предположим, что тролль заметил игрока. Как и солдат, тролль должен попросить слот на сетке у менеджера сцены, но поскольку тролли крупнее и мощнее чем простые люди, то вес тролля равен 8, что вдвое больше, чем у обычного солдата. Поскольку это все еще в пределах доступной игроку вместимости сетки, то менеджер сцены может назначить слот для тролля и уменьшить доступную вместимость сетки игрока до 0.
Теперь, когда в энкаутере появится другой солдат и запросит слот для нападения на игрока, то менеджер сцены не выделит ему место, так как его вес больше, чем доступная на данный момент вместимость сетки игрока. Этот второй солдат не может подойти к игроку и должен ждать за пределами зоны сетки.
Мощность атаки и вес работают аналогично, но для отдельных атак. Пока что в нашем примере и тролль, и солдат получили разрешение атаковать игрока, но они еще не выбрали, какую атаку использовать. Предположим, что вместимость атаки игрока равна 10. Тролль может выбрать две атаки: сильную атаку зарядом с весом атаки 6, и более слабую атаку дубиной с весом 4.
Поскольку вместимость атаки игрока равна 10, тролль может выбрать атаку зарядом и сообщить об этом менеджеру сцены, который уменьшит вместимость атаки игрока до 4. Теперь солдат выбирает одну из своих атак: выпад с весом 5 или взмах мечом с весом 3. Поскольку текущая вместимость атаки игрока равна 4, то солдат не может использовать выпад, поэтому на этот раз ему придется выбрать взмах мечом.
Отделы сетки, внутренний и внешние круги сетки
Хотя в системе часто упоминаются слоты сетки, проще думать о определяющих не равноудаленных от игрока позициях, образующих круги на некотором расстоянии. Упрощенно можно разделить сетку на внутренний и внешний круги, называя их «кругом атаки» и «кругом приближения» соответственно.
Радиус круга атаки определяется минимальной и максимальной дистанциями для атак ближнего боя. Когда враг впервые проходит проверку вместимости сетки и получает разрешение приблизиться к игроку, после чего перемещается, чтобы встать в круг приближения. Когда враг получает разрешение на выполнение определенной атаки (например, атаку у тролля или взмах меча у солдата), — он перемещается в круг атаки.
Тем временем враги, которые еще не прошли проверку вместимости сетки (как, например, второй солдат в нашем предыдущем примере), будут стоять за пределами круга приближения, ожидая своего шанса войти. Это поможет игроку определить, какие враги представляют непосредственную угрозу в ближнем бою.
Имплементация поведения
Чтобы в полной мере использовать преимущества системы сетки, враги могут иметь набор общих поведенческих паттернов, обеспечивающих соблюдение правил позиционирования и атаки. Это также повысило эффективность за счет переиспользования, так как любой враг, который может использовать атаки ближнего боя, будет использовать общие наборы поведения.
- Во-первых, любое существо, находящееся на дистанции, входящей во внешний круг, но не имеющее разрешения на атаку, должно как можно быстрее покинуть круг. Это поможет игроку понять, какие враги в принципе атакуют. Кроме того, враги не будут мешать и сталкиваться друг с другом при совершении атак.
- Враги, ожидающие за пределами сетки и не получившие в ней слот, не получают разрешение приблизиться. Вместо этого им будет указано местоположение слота, который в данный момент не занят. В таком случае враги будут пытаться встать перед указанным местом и будет создаваться ощущение естественного флангового поведения, без надобности «общения» друг с другом.
- Наконец, враги сразу после начала атаки отдают контроль над своим слотом обратно менеджеру сцены. Таким образом, менеджер может выстраивать очередь запросов на слоты для атаки и ротировать врагов, которым разрешено атаковать в течении энкаутера. Это не позволяет врагам хаотично перемещаться в зону ближнего боя игрока и выходить из нее, а также служит предохранителем от монополии на атаку одним врагом. В случае энкаутеров с небольшим количеством врагов, любой враг, уступивший свою позицию после совершения атаки, может быть тут же назначен обратно.
Масштабирование сложности
Вместимость сетки и вместимость атаки работают в синергии, чтобы ограничить количество и типы
врагов и атак, которые могут быть одномоментно обрушены на игрока. Одно из преимуществ этой системы является прямое масштабирование сложности, которое можно сделать за счет переменных вместительности игрока, а не ребалансом каждой отдельной сущности.
Если игрок захочет повысить сложность, можно просто увеличить переменные вместительности сетки и атаки. Изменение сетки позволит большему количеству существ окружить игрока во время боя, а изменение атаки позволит большему количеству врагов атаковать одновременно и/или использовать более тяжелые атаки.
Поддержание расположения сетки
Как уже говорилось, сетка world-related, поэтому слоты перемещаются вместе с игроком. Иногда это может приводить к ситуациям, когда слот, на который был назначен враг, перестает быть ближайшим к ним слотом. Такое часто случалось при резких перемещениях (дэш/ролл) игрока с атак врагов. Враги, которые ранее были определены как находящиеся в наилучшей позиции для атаки, также могут внезапно оказаться не самым лучшим выбором для нападения.
Для конкретного примера предположим, что игрок столкнулся с троллем и солдатом (как и ранее), а второй солдат ожидает атаки за пределами сетки. Если игрок внезапно двинется ко второму солдату, у которого в данный момент нет разрешения на атаку, будет лучше, если менеджер сцены сможет перенести разрешение с первого солдата на второго, чтобы воспользоваться новым положением второго солдата.
Чтобы решить эту проблему, менеджер сцены может «украсть» слот у одного существа, чтобы отдать его другому или иным образом переназначать существ по своему усмотрению. Можно сделать так, что враг с разрешением на атаку останется отдельно, если только назначенный враг не находится за пределами круга атаки, в то время как другой враг находится в пределах сетки. В этом случае назначение перейдет от одного врага к другому.
Такой переход может понадобиться чтобы переместить атакующих существ в другие позиции на сетке. Смещение позволит сократить общее время передвижения всех существ к назначенным им местам в бою. Кроме того, активно атакующие враги, могут «заблокировать» свое назначение менеджером сцены, чтобы не потерять свой слот во время атаки и случайно не перегрузить возможности игрока по атаке.
Алгоритм может выглядеть примерно так:
Список атаки = все атакующие существа
Для каждого существа в списке атак
Найдите ближайший слот для существа
Если ближайший слот заблокирован, продолжайте
Назначьте ближайший слот существу
Удалите существо из списка атак
Если ближайший слот уже был назначен,
Снимите назначение с этого существа
Дополнительные ограничения вражеских атак
Хотя вместимость атаки ограничивает общее количество атак, которые могут сработать одновременно, это не помешает врагам использовать одинаковый тип атаки, особенно на более высоких уровнях сложности, где вместимость атаки увеличена. Часто это может происходить в энкаутерах с множеством однотипных врагов.
Чтобы обеспечить разнообразие атак в каждом конкретном энкаутере можно ввести кулдауны на индивидуальном, типо-спецефичном и глобальном уровнях. Это не будет давать врагам друг за другом наносить слишком много одинаковых атак за конкретнный промежуток времени. Также это очень поможет сохранить баланс даже на высоком уровне сложности, потому что даже с увеличенной вместимостью атаки конкретные враги не могут использовать слишком много атак одного типа одновременно.
Также для повышения комфорта (или снижения дискомфорта) можно задавать особые параметры позиционирования врагов. Например, не разрешать врагам дальнего боя атаковать игрока в спину а заставлять их изначально встать хотя бы под углом обзора в 90 градусов. И опять же с таким параметром можно будет играться для других режимов сложности.
Заключение
Управление сложностью игры — область, заслуживающая серьезного рассмотрения и изучения. С одной стороны важно не перегрузить игрока только осваивающего игру. А с другой стороны нужно обеспечить сложный геймплей для более опытных игроков. Простые подходы основанные на сетке, могут помочь геймдизайнерам создавать разнообразные и интересные ситуации, которые можно масштабировать для множества уровней сложности, регулируя буквально парочку значений.
Хотя реализация этого алгоритма достаточно проста, его реальная сила заключается в том, что все просчеты делегированы менеджеру сцены. Можно задать простое поведение, позволяющее перемещение персонажей из слотов, которые им не назначены, чтобы заставить персонажей обходить друг друга с флангов и избегать друг друга. Плюс этот алгоритм может быть без труда совмещен с системой influence mapping, чтобы помочь персонажам не сталкиваться и в целом перемещаться по полю боя более естественно.
Другие техники также могут быть объединены с идеей сетки боя, чтобы адаптировать механику к потребностям конкретной игры, сохраняя при этом гибкость уровней сложности.
Бонус: EQS в Unreal Engine
Если вы работаете с Unreal Engine то можно посмотреть их встроенную систему Environment Query System. Эта система является близкой по смыслу к тому, что было изложено в этой статье. По сути своей EQS это сбор сведений из окружающей среды, запрашиваемый деревом поведения ИИ и получаемый обратно им же.