Не в моем случае потому, что и в шутерах от первого лица, и в шутерах от третьего лица, на примере которых сделаны все туториалы, игрок смотрит на происходящее с той же высоты, на которой находятся противники. И пока третья координата камеры не меняется - все ок. Но в моем случае она приподнята над полем боя, и вот здесь все идет не так и я никак не могу найти правильного решения этой проблемы.
В чем заключается идея... рендерить текст как 3д-объект, чтобы он мог следовать за персонажем (по факту привязывать нужно к основной кости персонажа, иначе будут артефакты) и скалироваться в зависимости от удаленности персонажа на поле боя (для больших боев это будет актуально), а чтобы текст в итоге выглядел как 2д-объект - в каждом кадре поворачивать его таким образом, чтобы он всегда смотрел в камеру, тогда он будет выглядеть как плоский, по сути таковым не являясь. Это действительно работает при камере на уровне глаз любого из персонажей, а в моем случае выглядит так, как на скриншоте выше.
Кроме того 3д-объект - есть 3д-объект, по умолчанию он реагирует на освещение, отбрасывает тени и т.п.. Для компенсации всего этого безобразия вам нужно будет сделать кастомный материал, который будет сконфигурен под 2D GUI и будет отключать тени, декали, освещение и прочие фишки из 3д-мира.
Ну и, вишенка на торте - в Unreal Engine очень криво работает наследование на уровне блюпринтов. Вы добавляете 2D-компонент в базовый класс персонажа, ожидаете, что он появится у всех наследников, запускаете игру - и он отображается над тремя из пяти. Идете проверять - компоненты, вроде, есть у всех, но отображаются только у половины. В итоге, как подсказал, ChatGPT, это очень частая "особенность" движка - он может по одному ему известной логике и без явной на то причины оверрайдить настройки компонент в наследниках. В моем случае это и произошло - компонент добавился, но вот его настройки, почему-то прокинулись только в 3 наследника из 5. Таких подстав обычно не ожидаешь и понять что происходит при тоннах галочек и прочих настроек в каждом объекте интерфейса бывает достаточно сложно.
В чем заключается идея... рендерить текст как 3д-объект, чтобы он мог следовать за персонажем (по факту привязывать нужно к основной кости персонажа, иначе будут артефакты) и скалироваться в зависимости от удаленности персонажа на поле боя (для больших боев это будет актуально), а чтобы текст в итоге выглядел как 2д-объект - в каждом кадре поворачивать его таким образом, чтобы он всегда смотрел в камеру, тогда он будет выглядеть как плоский, по сути таковым не являясь. Это действительно работает при камере на уровне глаз любого из персонажей, а в моем случае выглядит так, как на скриншоте выше.
Кроме того 3д-объект - есть 3д-объект, по умолчанию он реагирует на освещение, отбрасывает тени и т.п.. Для компенсации всего этого безобразия вам нужно будет сделать кастомный материал, который будет сконфигурен под 2D GUI и будет отключать тени, декали, освещение и прочие фишки из 3д-мира.
Ну и, вишенка на торте - в Unreal Engine очень криво работает наследование на уровне блюпринтов. Вы добавляете 2D-компонент в базовый класс персонажа, ожидаете, что он появится у всех наследников, запускаете игру - и он отображается над тремя из пяти. Идете проверять - компоненты, вроде, есть у всех, но отображаются только у половины. В итоге, как подсказал, ChatGPT, это очень частая "особенность" движка - он может по одному ему известной логике и без явной на то причины оверрайдить настройки компонент в наследниках. В моем случае это и произошло - компонент добавился, но вот его настройки, почему-то прокинулись только в 3 наследника из 5. Таких подстав обычно не ожидаешь и понять что происходит при тоннах галочек и прочих настроек в каждом объекте интерфейса бывает достаточно сложно.
Спустя множество проб и экспериментов, пока остановился на таком варианте. Подытожим:
- Первая строчка - имя персонажа, неотъемлемая часть.
- Ее цвет - оттенки синего (синяя команда), красного (красная команда), зеленого (наш пользователь).
- Под ником чуть мельче отображаем здоровье и энергию в формате текущее/максимальное. Возможно тут, все же, стоит добавить бары, но пока не буду.
- Ключевой момент, который мне подсказал Олег - весь текст имеет черную обводку толщиной в 2 пикселя. Это нужно для того, чтобы текст был хорошо различим как на темном, так и на светлом фоне. Кроме того без этого текст в принципе поверх 3D-картинки читается плохо.
Проблему здесь вижу только одну - не понятно за какую сторону играет наш пользователь. Возможное решение - использовать для идентификации команды отдельный спрайт (кружок, треугольник) разного цвета вместо цвета шрифта. Пока оставлю этот вопрос открытым.
- Первая строчка - имя персонажа, неотъемлемая часть.
- Ее цвет - оттенки синего (синяя команда), красного (красная команда), зеленого (наш пользователь).
- Под ником чуть мельче отображаем здоровье и энергию в формате текущее/максимальное. Возможно тут, все же, стоит добавить бары, но пока не буду.
- Ключевой момент, который мне подсказал Олег - весь текст имеет черную обводку толщиной в 2 пикселя. Это нужно для того, чтобы текст был хорошо различим как на темном, так и на светлом фоне. Кроме того без этого текст в принципе поверх 3D-картинки читается плохо.
Проблему здесь вижу только одну - не понятно за какую сторону играет наш пользователь. Возможное решение - использовать для идентификации команды отдельный спрайт (кружок, треугольник) разного цвета вместо цвета шрифта. Пока оставлю этот вопрос открытым.
Media is too big
VIEW IN TELEGRAM
Ну... уже, как будто бы, становимся похожими на игру.
This media is not supported in your browser
VIEW IN TELEGRAM
Нашел подходящий пак анимаций для пистолетов. Да, их полно в сторе, но в основном для одного пистолета в руках (что логично). В моем случае нужна поддержка двух и этот набор кажется достаточно простым и достаточным. Анимации не самого высокого качества, конечно, но здесь есть все необходимое (достал/убрал/прицелился/две разных одноручных атаки/одна атака из двух пистолетов/перезарядка). Итого +11 евро к расходам.
На Fab есть и более специализированная подборка из 116 анимаций исключительно для двух пистолетов (https://www.fab.com/listings/32adc703-4c19-4dad-911b-196f76511d77), причем вдвое дешевле, но она мне показалась излишне пафосной и слабо применимой к моему случаю.
На Fab есть и более специализированная подборка из 116 анимаций исключительно для двух пистолетов (https://www.fab.com/listings/32adc703-4c19-4dad-911b-196f76511d77), причем вдвое дешевле, но она мне показалась излишне пафосной и слабо применимой к моему случаю.
This media is not supported in your browser
VIEW IN TELEGRAM
Как выглядит работа со сторонними анимациями из стора.
Совместимость анимаций - пока моя самая большая боль в этом проекте, о причинах и последствиях я писал уже много на этом канале, желающие смогут найти в истории.
Но даже если вам повезло изначально иметь все модели со стандартным скелетом от Unreal Engine (не мой случай, я делаю риг в Mixamo) - расслабляться все равно рано. Дело в том, что в UE4 скелет один (Mannequin), а в UE5 - два: мужской (Manny) и женский (Quinn) (почему-то все время напоминает Фредди Мак и Фенни Мэй, для тех кто в теме). Конечно они разные, в UE5 больше костей для лучшей детализации движений и они разделены по полу, потому что мужская и женская анатомия существенно отличаются. Не сложно догадаться под какой скелет сделано больше всего анимаций в сторах, ведь он старше и универсальнее.
Короче у меня уже солянка из 4х скелетов - Mixamo (основной), кастомный (старые анимации), Manny и Mannequin. И накладываются они друг на друга уже далеко не идеально...
Совместимость анимаций - пока моя самая большая боль в этом проекте, о причинах и последствиях я писал уже много на этом канале, желающие смогут найти в истории.
Но даже если вам повезло изначально иметь все модели со стандартным скелетом от Unreal Engine (не мой случай, я делаю риг в Mixamo) - расслабляться все равно рано. Дело в том, что в UE4 скелет один (Mannequin), а в UE5 - два: мужской (Manny) и женский (Quinn) (почему-то все время напоминает Фредди Мак и Фенни Мэй, для тех кто в теме). Конечно они разные, в UE5 больше костей для лучшей детализации движений и они разделены по полу, потому что мужская и женская анатомия существенно отличаются. Не сложно догадаться под какой скелет сделано больше всего анимаций в сторах, ведь он старше и универсальнее.
Короче у меня уже солянка из 4х скелетов - Mixamo (основной), кастомный (старые анимации), Manny и Mannequin. И накладываются они друг на друга уже далеко не идеально...
А еще в паке меня ждал вот такой сюрприз... анимации крайне гранулированы.
Т.е. там, где я ожидал одну анимацию выстрела из двух пистолетов - получил 4 (начало атаки, выстрел левой рукой, выстрел правой рукой, окончание атаки). Есть в наборе также просто анимация выстрела с двух рук сразу, но сами посмотрите как она выглядит на видео из предыдущего поста...
В целом это и плохо, и хорошо. Плохо тем, что я получил конструктор вместо готового ассета, хорошо тем, что из него я смогу собрать более приличную анимацию, где выстрел с обеих рук происходит, хотя бы, не абсолютно одновременно. Это добавит немного реализма, но займет существенно больше времени на интеграцию. Ну да ладно, я никуда не тороплюсь...
Но вывод, ребята, здесь один: если есть возможность - лучше начинать проект сразу со своим аниматором в команде, потому что на эти танцы с бубном уходит слишком много времени.
Т.е. там, где я ожидал одну анимацию выстрела из двух пистолетов - получил 4 (начало атаки, выстрел левой рукой, выстрел правой рукой, окончание атаки). Есть в наборе также просто анимация выстрела с двух рук сразу, но сами посмотрите как она выглядит на видео из предыдущего поста...
В целом это и плохо, и хорошо. Плохо тем, что я получил конструктор вместо готового ассета, хорошо тем, что из него я смогу собрать более приличную анимацию, где выстрел с обеих рук происходит, хотя бы, не абсолютно одновременно. Это добавит немного реализма, но займет существенно больше времени на интеграцию. Ну да ладно, я никуда не тороплюсь...
Но вывод, ребята, здесь один: если есть возможность - лучше начинать проект сразу со своим аниматором в команде, потому что на эти танцы с бубном уходит слишком много времени.
Снова меня догнала необходимость рефакторинга, иначе дальше двигаться будет сложно.
Решил заняться разработкой набора высокоуровневых функций, чтобы с системой боя было работать просто, не задумываясь каждый раз о необходимых анимациях, стойках/атаках под разные типы оружия и т.п..
В результате хочу избавиться от методов типа СтреляйИзВинтовки() и СтреляйСДвухПистолетов() и иметь один метод Атакуй() на все случаи жизни, который будет понимать с каким оружием в руках находится персонаж и запускать необходимые анимации, в т.ч. на цели (попал, увернулся, заблокировал). То же самое касается боевых стоек - при экипировке оружия буду определять его тип и автоматически переключаться на соответствующую анимацию, в т.ч. по завершении действий вроде передвижений, атак, уворотов, уронов, блоков.
Все это займет какое-то время, такие вещи в блюпринтах делать очень неудобно и, как водится, демонстрабельного результата такая работа не принесет, поэтому на следующей неделе разберу немного околотехнических деталей по прошлому ролику для тех, кто подписан на канал для пассивного и неспешного изучения особенностей игростроя в целом, и на UE в частности.
Решил заняться разработкой набора высокоуровневых функций, чтобы с системой боя было работать просто, не задумываясь каждый раз о необходимых анимациях, стойках/атаках под разные типы оружия и т.п..
В результате хочу избавиться от методов типа СтреляйИзВинтовки() и СтреляйСДвухПистолетов() и иметь один метод Атакуй() на все случаи жизни, который будет понимать с каким оружием в руках находится персонаж и запускать необходимые анимации, в т.ч. на цели (попал, увернулся, заблокировал). То же самое касается боевых стоек - при экипировке оружия буду определять его тип и автоматически переключаться на соответствующую анимацию, в т.ч. по завершении действий вроде передвижений, атак, уворотов, уронов, блоков.
Все это займет какое-то время, такие вещи в блюпринтах делать очень неудобно и, как водится, демонстрабельного результата такая работа не принесет, поэтому на следующей неделе разберу немного околотехнических деталей по прошлому ролику для тех, кто подписан на канал для пассивного и неспешного изучения особенностей игростроя в целом, и на UE в частности.
RustyBits | Дневник разработчика
Ну... уже, как будто бы, становимся похожими на игру.
Давайте разберем несколько интересных аспектов того, что было реализовано в этом видео.
1. Медленно проявляющиеся сетка боя и HUD (имя игрока, жизнь, энергия).
Здесь примечательно то, что для одного и того же действия на объектах разного типа понадобилось реализовать два разных подхода.
- С HUD все просто - создаем клип анимации на поле Render Opacity корневого канваса, где в нулевой секунде значение равно 0, на первой секунде - 1. Проигрываем анимацию в нужный момент.
- С сеткой пришлось повозиться чуть больше - создать в материале гекса глобальную переменную для контроля прозрачности, потом увеличивать ее с нуля до единицы в таймлайне (уже разбирал как это работает выше). По факту в итоге получаем то же самое, просто для HUD есть стандартный подход, а для материалов - нет. Но более интересным здесь было другое: каждый гекс в сетке - отдельный объект и, конечно же, мне не хотелось их все обходить циклом и менять прозрачность каждого. На помощь приходит особенность работы самого движка - материалы в памяти не дублируются. Т.е. если мы создаем инстансы одной модели на сцене - все они переиспользуют один инстанс материала в памяти. А значит получив ссылку на материал из одного инстанса модели и изменив в нем что-то - мы меняем это значение всем объектам на сцене, использующим этот материал. Другими словами здесь вообще ничего не пришлось придумывать для реализации функции проявления всего боя, это сработало автоматически, как только я изменил свойство материала первого попавшегося гекса. Но как тогда манипулировать свойствами материала одной конкретной ячейки? И что тогда делать если у меня будет две одинаковых модели персонажа на сцене, которые я захочу подсветить по-разному или даже применить на них разные текстуры?
1. Медленно проявляющиеся сетка боя и HUD (имя игрока, жизнь, энергия).
Здесь примечательно то, что для одного и того же действия на объектах разного типа понадобилось реализовать два разных подхода.
- С HUD все просто - создаем клип анимации на поле Render Opacity корневого канваса, где в нулевой секунде значение равно 0, на первой секунде - 1. Проигрываем анимацию в нужный момент.
- С сеткой пришлось повозиться чуть больше - создать в материале гекса глобальную переменную для контроля прозрачности, потом увеличивать ее с нуля до единицы в таймлайне (уже разбирал как это работает выше). По факту в итоге получаем то же самое, просто для HUD есть стандартный подход, а для материалов - нет. Но более интересным здесь было другое: каждый гекс в сетке - отдельный объект и, конечно же, мне не хотелось их все обходить циклом и менять прозрачность каждого. На помощь приходит особенность работы самого движка - материалы в памяти не дублируются. Т.е. если мы создаем инстансы одной модели на сцене - все они переиспользуют один инстанс материала в памяти. А значит получив ссылку на материал из одного инстанса модели и изменив в нем что-то - мы меняем это значение всем объектам на сцене, использующим этот материал. Другими словами здесь вообще ничего не пришлось придумывать для реализации функции проявления всего боя, это сработало автоматически, как только я изменил свойство материала первого попавшегося гекса. Но как тогда манипулировать свойствами материала одной конкретной ячейки? И что тогда делать если у меня будет две одинаковых модели персонажа на сцене, которые я захочу подсветить по-разному или даже применить на них разные текстуры?
Дело в том, что структура материалов трехуровневая:
1. Material - можно относиться к этому как к базовому классу, который описывает поведение шейдера.
2. Material Instance - не смотря на то, что в названии фигурирует слово "instance", это не совсем экземпляр. Он тоже создается в дизайн тайме и представляет собой параметризованный Material. Т.е. я могу, например, создать сложное поведение шейдера в Material, а потом создать несколько Material Instance, которые оверрайдят текстуры и прочие параметры для того, чтобы результат выглядел иначе, но логика полностью наследуется от Material. Я думаю о Material Instance как о наследнике Material.
3. А еще есть Dynamic Material Instance - создается только в рантайме и вот это уже действительно инстанс.
Другими словами для того, чтобы подсветить одну ячейку, как это сделано в конце видео, необходимо получить нужную ячейку, взять у нее материал, создать из него dynamic material instance, присвоить обратно ячейке и манипулировать свойствами уже этого конкретного экземпляра материала. Вот такая, не сразу очевидная, но вполне логичная, оптимизация.
1. Material - можно относиться к этому как к базовому классу, который описывает поведение шейдера.
2. Material Instance - не смотря на то, что в названии фигурирует слово "instance", это не совсем экземпляр. Он тоже создается в дизайн тайме и представляет собой параметризованный Material. Т.е. я могу, например, создать сложное поведение шейдера в Material, а потом создать несколько Material Instance, которые оверрайдят текстуры и прочие параметры для того, чтобы результат выглядел иначе, но логика полностью наследуется от Material. Я думаю о Material Instance как о наследнике Material.
3. А еще есть Dynamic Material Instance - создается только в рантайме и вот это уже действительно инстанс.
Другими словами для того, чтобы подсветить одну ячейку, как это сделано в конце видео, необходимо получить нужную ячейку, взять у нее материал, создать из него dynamic material instance, присвоить обратно ячейке и манипулировать свойствами уже этого конкретного экземпляра материала. Вот такая, не сразу очевидная, но вполне логичная, оптимизация.
Закончу историю материалов функцией мигания ячейки.
Здесь интересно то, что в ней не используется анимация или таймлайн, она полностью реализована через блюпринт материала, т.е. через шейдер (прилагаю скриншот), и выполняется на GPU.
Как это работает:
- Берем две текстуры (основная белая + желтая с изображением передвижения) и постепенно их смешиваем, используя функцию Lerp (линейная интерполяция), степень смешивания задаем параметром Alpha
- Чтобы не просто морфились текстуры, а также добавлялся эффект свечения - дополнительно делаем Lerp между полной прозрачностью и нужным цветом с той же Alpha и присваиваем результат в Emissive Color материала
- Саму Alpha, чтобы она менялась во времени, нужно зациклить по синусоиде - для этого берем Time (тики, прошедшие с запуска игры), передаем в функцию Sine (сделает из этого числа синусоиду от -1 до 1), к результату добавляем 1 и делим пополам, чтобы конвертировать в синусоиду от 0 до 1, которая и нужна для правильной работы процента смешивания.
Здесь интересно то, что в ней не используется анимация или таймлайн, она полностью реализована через блюпринт материала, т.е. через шейдер (прилагаю скриншот), и выполняется на GPU.
Как это работает:
- Берем две текстуры (основная белая + желтая с изображением передвижения) и постепенно их смешиваем, используя функцию Lerp (линейная интерполяция), степень смешивания задаем параметром Alpha
- Чтобы не просто морфились текстуры, а также добавлялся эффект свечения - дополнительно делаем Lerp между полной прозрачностью и нужным цветом с той же Alpha и присваиваем результат в Emissive Color материала
- Саму Alpha, чтобы она менялась во времени, нужно зациклить по синусоиде - для этого берем Time (тики, прошедшие с запуска игры), передаем в функцию Sine (сделает из этого числа синусоиду от -1 до 1), к результату добавляем 1 и делим пополам, чтобы конвертировать в синусоиду от 0 до 1, которая и нужна для правильной работы процента смешивания.