Разработка ремейка методами чайника 7. Система повреждений и ручное прицеливание

Здравствуйте дорогие подписчики и читатели цикла статей о разработке лайт-ремейка Корсаров. В этой части я поведаю вам о том, как мне пришлось переделывать прошлую логику повреждения парусов, о создании режима ручного прицеливания на цель, и немного информации по обновленной архитектуре компонентов корабля.

И снова проблемные паруса

Как вы помните из прошлой части, я создавал новый прототип паруса, построенный на костевой анимации под каждый вертекс. В целом реализация казалась осуществимой, и дальше вопрос возникал только с тем, как реализовать создание коллизии с нормальной производительностью. Поскольку UE предлагает создавать коллизию либо только под корневую кость, либо сразу под все кости, я решил что нужно создать анимацию которая будет работать как и ранее, за счет Morph Target-ов и отдельного перемещения небольшого количества костей на которых будет коллизия. В общем логика близкая к анимации поднятия/спуска парусов.

Но как обычно не обошлось без проблем: чертов UE не желает добавлять коллизию на кости, которые никак не связаны с вертексами. Ок, значит завяжем их в блендере - подумал я. И конечно же с этим тоже возникли проблемы. Если ты завязываешь кости на вертексы, а затем двигаешь вертексы через Shape Keys, то разумеется ничего работать не будет. Придется делать анимацию одновременно с деформацией модели и перемещением костей. В общем, это начало приносить довольно много головной боли, и мне не хотелось бы потерять на этом слишком много времени зря, если опять окажется что я зашел в тупик…

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

Я решил еще раз пробежаться по своему планируемому решению. И в процессе этого я начал тестировать производительность. Выставил шесть парусов подряд и начал косплеить пирата из Острова сокровищ, в итоге моим глазам предстали дропы фпс со 120 до 40. Это. Просто. Катастрофа! Я конечно знал что используемое решение называют дорогим, но чтобы настолько… А теперь представим что какой-нибудь линейный корабль дает залп борта из 40 с чем-то пушек прямо по парусам. Как бы фпс до однозначных значений не падал… В общем стало очевидно, что перед тем, как решать проблемы с коллизией, необходимо найти нормальное производительное решение для повреждения парусов.

Если кому интересно, что конкретно настолько сильно грузит систему, то это функция Capture Scene.
Если кому интересно, что конкретно настолько сильно грузит систему, то это функция Capture Scene.

И снова поиск решения

На этом моменте, я решил что возможно некоторые комментаторы правы, и не стоит гнаться за точностью попаданий? Но паруса могут быть довольно большими, и будет некрасиво, если попадание в правую часть будет создавать отверстие в левой. Тогда мне в голову пришла мысль, что я вполне могу разделить парус условно на 4 части, определять в какую часть произошло попадание, и затем там уже рисовать повреждение в рандомно выбранной точке с заранее заданным положением.

Для того чтобы это сделать, необходимо получить координаты середины паруса. В блупринтах есть возможность получить координаты границы в которой находится модель через функцию Get Actor Bounds. Но проблема возникает, если парус имеет нестандартную квадратную форму, тогда середина паруса может сильно смещаться к одному из краев. Кажется пришло время расчехлять костыли…

Поскольку коллизия на парусе точно совпадала с его формой в спокойном состоянии, то я решил применить функцию Get Closest Point On Collision. Логика получалась следующая: перебрать все вершины границы, затем под каждую из них вызвать функцию получения ближайшей точки в коллизии. В результате получилось относительно неплохо получить середину:

Черная граница это логика из Get Actor Bounds, а белые шары по краям - Get Closest Point On Collision. Шар посередине - рассчитываемый.
Черная граница это логика из Get Actor Bounds, а белые шары по краям - Get Closest Point On Collision. Шар посередине - рассчитываемый.

Можно ли сделать определение точнее? Можно, если из блендера импортировать локаторы, которые отвечают за размещение парусов, либо написать на плюсах нужную функцию. Но действительно ли мы готовы сдаться и жить в проклятом мире которые сами же и создали…?

Сто шагов назад

В прошлой статье я рассказывал о методе Find collision UV, который не работает со Skeletal Mesh. На самом деле сообщество написало собственное решение для этой проблемы. Но решение это на С++, а я очень не хотел залезать в плюсы в процессе создания прототипа, в частности потому, что у моей любимой IDE не было нормальной интеграции с UE, а писать на Visual Studio - чистого вида мазохизм. Но смотря обучающее видео по работе с плюсами в UE, я увидел у автора мою любимую IDE! Как оказалось, они не так давно выпустили нужную интеграцию и теперь можно жить!

В общем я настроил IDE, скопировал код с форума, скомпилировал, запустил новосозданную функцию и… вылетел с фатальной ошибкой. Разумеется так просто все не будет, все таки код этот писался еще под 4.х версии… Немного поковырявшись, мне удалось убрать ошибку, но получение нужных мне координат по прежнему не работало. Стало быть придется самому со всем разбираться? Как бы не так! Открыв гитхаб, я начал вести поиск по названию функций и переменных из кода форума. И каким-то чудом вышел на эту рабочую версию. Да хранят Богини гитхаб!

Получая координату текстуры через новую функцию, я делаю поиск ближайшей маски на парусе и применяю её. Если ближайшая маска уже применена, то я просто игнорирую повреждение. Для книпелей будет подбираться 2 маски. Остается только вопрос, как применять логику скилов по типу “Увеличения повреждений парусам”...

Рисование реализовано по туториалу https://www.youtube.com/watch?v=3FY0lboNv9w

Производительность у решения хорошая, мне не удалось уронить фпс ниже 100 кадров и при 10 парусах подряд. Окей, тогда вернемся к созданию гибкой коллизии?

А нет, не вернемся. Я порядком устал возиться с этими чертовыми парусами! Пока что, сойдемся на том, что попадание по раздутому ветром парусу сбоку, не наносит ему урона, из-за того что парус просто прогибается под снаряд. Таким образом, я оставляю старую логику анимаций, а вопрос с коллизиями думаю в будущем будет проще решить через пресеты состояния паруса.

Единственный момент с коллизией, который пришлось покрывать кодом, это процесс спуска парусов, чтобы снаряд не проделывал отверстий в спускающемся парусе. Это сделано простым сложением текущей позиции анимации, и места куда попал снаряд. Если сумма превышает или равна 1, значит снаряд промахнулся. В общем это проще увидеть:

Архитектурные метаморфозы

Ранее, паруса являлись простыми скелетал мешами, которые были закреплены за мачтами, но поскольку паруса теперь имеют довольно важную логику, я принял решение вынести их в отдельный актор BP_Sail, который находится под BP_Mast. Также, я решил последовать совету из комментариев к прошлому посту, и создавать акторы BP_Cannon под каждый пушечный сокет. В свою очередь от BP_Cannon наследуются пушки разного калибра, в которых указаны их уникальные характеристики. В качестве снаряда создан актор BP_CannonBall, который имеет наследников со своими показателями урона и дальности.

Упрощенная UML-схема, без учета логики звуковых эффектов
Упрощенная UML-схема, без учета логики звуковых эффектов

Ручная наводка на цель

В прошлый раз я нашел видео с логикой предпросмотра траектории снаряда, но оно не очень подходило мне из-за своей минималистичности. В примере рисуются простые блоки с интервалом, а мне же необходимо нарисовать дугу. Но ютуб как будто читал мои мысли, и в какой-то момент выдал в рекомендуемых видео с реализацией в виде плавной кривой. Храните Богини индусов!

Но перед тем, как делать отрисовку пути, нужно определиться каким образом переключаться между разными бортами для наводки. Я решил что будет неплохо, если при повороте камеры на соответствующий борт, и при нажатии на правую кнопку мыши, камера сдвигалась бы на палубу корабля, откуда уже можно прицеливаться. Но можно ли это сделать автоматически без лишних телодвижений? Предположим что мы сможем рассчитать ширину и длину палубы, затем будем менять положение камеры через вычисления. Но скорее всего выглядеть это будет криво, и подобрать идеальное положение для всех кораблей - не выйдет. Таким образом, я принял решение вручную устанавливать положения камер для кораблей. Для боковых камер я дополнительно установил увеличенный градус обзора с 90 до 120, чтобы можно было видеть всю зону поражения.

Синие объекты на палубе и в носу корабля - это и есть камеры
Синие объекты на палубе и в носу корабля - это и есть камеры

А вот на моменте создания кривой возникли проблемы… Автор видео для отрисовки траектории использовал функцию Predict Projectile Path, которая на вход должна принимать вектор по которому планируется запускать снаряд. Вектор у него рассчитывается манипуляциями с координатами и вообще никак не учитывает максимально возможную скорость снаряда. Я же в обычной стрельбе использовал функцию Suggest Projectile Velocity, которая на вход принимает точку куда необходимо попасть и скорость снаряда. Таким образом, невозможно попасть в точку превышающую максимальную дальность.

Тогда я решил поступить следующим образом: предположим что со стороны корабля выходит точка, которая будет указывать на поверхность моря. Мы указываем эту точку в функцию Suggest Projectile Velocity, затем получившийся вектор прокидываем в функцию Predict Projectile Path, и в итоге получаем правильную траекторию. Затем эту точку двигаем по поверхности в зависимости от положения мыши.

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

Проснувшись на следующий день, я еще раз посмотрел на получившийся результат, и выкинул его куда подальше, потому что мне было бы неудобно это использовать в динамике. Попробуем подумать еще раз. И тут меня осенило: функции Suggest Projectile Velocity ведь не обязательно указывать точку на поверхности моря, я могу указать точку в метре от пушки и тем самым задать вектор направления с учетом скорости снаряда. Немного пошаманив с кодом, у меня получилось сделать корректную траекторию.

Впрочем, есть некоторая проблема: если стрелять в автоматическом режиме, то все снаряды стремятся попасть точно в середину вражеского корабля, но если мы стреляем с ручной наводкой, то снаряды ложатся согласно своему расположению на борту. Я пока что не уверен, стоит ли пытаться это починить, или оставить это своеобразной фичей… оставляйте ваше мнение в комментариях.

Стрельба из пушек разными снарядами кроме книпелей, им еще следует настроить отдельное целепостроение на паруса.

К сожалению я до сих пор не сделал визуальных эффектов попадания по кораблю, ибо у меня недостает мотивации начать изучение ниагары. В целом архитектура под это уже построена, просто нужно сесть и заняться этим. Но не думаю что это будет темой следующей статьи. Поскольку я хочу создать тестовый билд с базовыми механиками морского сражения к концу февраля, то пока что сосредоточусь на создании интерфейса, чтобы было где отрисовывать состояние корабля. Затем довольно остро стоит вопрос с созданием ИИ под вражеский корабль, и вот тут наверное прямо совсем беда будет…

Почему вышла задержка со статьей:

Я порядком затянул со сроками, потому что словил Tropicallove forever moment(кто смотрел Bocchi the rock - тот поймет). Посмотрел одно романтическое аниме, потом второе, потом уже залипал в визуальную новеллу, и в конце-концов упал в колодец депрессии по бессмысленно потерянной юности… Но кого это заботит в общем-то…

Если вам понравилась статья, то буду благодарен за ваши комментарии, вопросы или советы!

7272
23 комментария

Комментарий недоступен

7
Ответить

Кайф, подписался

3
Ответить

Подписался. Интересно.

3
Ответить

Харош, продолжай)

3
Ответить

Это гта 7?

1
Ответить

ничего не понял, но очень интересно, хочется самому аж попробовать

1
Ответить

Первое что приходит в голову:

1
Ответить