RustyBits | Дневник разработчика
198 subscribers
106 photos
47 videos
7 files
11 links
Дневник разработчика мобильной игры Genesyx
Download Telegram
This media is not supported in your browser
VIEW IN TELEGRAM
Какой же плохой была идея делать проект без аниматора...

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

Давайте разберем по порядку:

1. Обратите внимание на передвижение бойца в конце видео - поворот влево, перемещение, поворот вправо.
Насколько оно реалистично? На мой взгляд - ни насколько... В реальности человек бы просто передвинулся наискосок. Но... у меня нет такой анимации. И нигде нет, ее можно только сделать под заказ.

То, что намутил здесь я - это примерно то, что делали в играх конца 90х и начала 2000х. Да, сейчас уже существует locomotion, о нем я уже рассказывал ранее и, более того, есть locomotion паки, которые можно брать прямо с Mixamo и использовать. Пересмотрел все - ни один не подошел. В основном потому, что во всех них повороты сделаны в состоянии покоя, а мне нужно мало того, что в боевых стойках, причем разных (сравните Альвареса, Ирвинга и ГГ например), так еще и достаточно быстро, чтобы не затягивать анимациями динамику игры. Также существуют технологии хитрого смешивания анимаций, когда, например, верхняя часть движется по логике одной анимации, а нижняя - по другой. Но это уж точно оставлю профессионалам своей области.

Кроме locomotion паков на Mixamo есть куча анимаций поворота, но угадайте какая с ними проблема, кроме того, что 95% из них тоже совершаются из состояния покоя? Они сделаны для поворотов на 45-90-180 градусов. На моем гексагональном поле боя повороты нужны на 60-120-180 градусов, т.е. что-то реально готовое я могу взять только для 180, да и то будут вопросы к стойке.

Что я в итоге сделал...
- Взял единственную анимацию поворота влево на 90 градусов, похожую на основную боевую стойку, из Mixamo.
- Сделал ее отраженную версию через функцию мирроринга, чтобы у меня была также анимация поворота вправо (почему-то именно эта анимация поворота на Mixamo только в одном варианте).
- В блюпринте персонажа написал следующую логику: проигрываем анимацию поворота налево на 70%, после этого морфим ее в анимацию передвижения, по окончании анимации передвижения морфим ее в анимацию поворота вправо, которую также проигрываем на 70% и морфим обратно в боевую стойку.
- Еще одна беда в этом подходе в том, что не смотря на то, что и в анимации поворота, и в анимации передвижения, не используется root motion - анимации поворачивают и двигают основную кость (pelvis - эту проблему тоже разбирал ранее), а значит когда персонаж поворачивается - реальный поворот его объекта на сцене не меняется, как и не меняются его координаты при перемещении персонажа анимацией передвижения. Т.е. в моменты переключения анимаций приходится еще вручную менять координаты меша на сцене, не забыв при этом про DelayUntilNextTick между изменением координат и запуском следующей анимации, чтобы логика успела отработать и мы не увидели прыгающего персонажа в одном кадре..

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

Радует и то, что даже это явно получилось существенно лучше того, с чего я начинал здесь.

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

О других проблемах и выводах в следующих постах.
Следующий блок проблем - выстрел Альвареса по Ирвингу:

1. Обратите внимание под каким углом Альварес стоит к Ирвингу при выстреле. Есть у него шансы попасть? Конечно нет жесткого требования всем персонажам стоять именно под углом 60 градусов друг к другу, но если здесь добиваться реализма - придется еще больше нагружать анимации перемещения поворотами, потому что перемещаться-то все равно нужно под углом в 60 градусов.

2. Анимация выстрела и эффект огня не синхронизированы. Т.е. отдача от выстрела и огонь живут каждый своей жизнью - огонь просто появляется с определенной периодичностью, начинается и заканчивается в подходящий момент анимации, подобранной на глаз. Это можно исправить через триггеры анимации, чтобы эффект срабатывал именно при отдаче, но если я буду заниматься еще и такими вещами при текущем размере "команды" - у проекта совсем не будет шансов выйти.

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

4. Нет эффекта попадания в Ирвинга, из-за этого анимацию получения урона можно спутать с уклонением. Но тут снова возникает куча вопросов:
- Добавление эффекта искр сделает еще более очевидной проблему #1
- В каком месте его добавлять? В районе корпуса? А потом я добавлю механику повреждений головы, корпуса, рук, ног и получится визуальное попадание в корпус, но повреждена нога. А если добавлять эффекты на фактических частях тела - выглядеть будет совсем как бред, потому что атакующий стреляет ровно.

Мне кажется пора уже отказаться от идеи реализма и просто найти и принять какой-то приемлемый уровень условностей.
Media is too big
VIEW IN TELEGRAM
Похоже моя борьба с перфекционизмом обречена на поражение.

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

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

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

1. Просадка FPS когда камера меняет крупный план на общий. Назвали дважды.
Честно говоря удивился, что эта анимация воспринимается как падение FPS. Дело в том, что по умолчанию трансформация одного положения камеры в другое производится через линейную интерполяцию координат (функция LERP), за счет этого получается эффект быстрого движения камеры вначале, постоянно замедляющегося к концу. FPS же при этом остается неизменным. Это можно решить через использование другой функции, которая будет трансформировать координаты равномерно, но я не уверен, что это будет смотреться лучше. Пока не буду трогать.

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

3. Вспышка выстрела находится слишком близко к стволу. Подвинул подальше.

4. Походка бойца "в раскорячку" - это артефакт различных скелетов. Как я уже упоминал ранее, анатомия мужчины и женщины довольно существенно различается, т.е. длина различных костей и расстояние между ними разные. Точно также отличаются скелеты моих моделей от стандартных в Unreal Engine и от тех, что генерит Mixamo. Выход здесь один - все анимации делать под заказ, причем мужские и женские отдельно. Может быть когда-то это нужно будет сделать, но точно не до фазы запуска проекта. Пока оставляю это зло как неизбежное.

5. Эффект выстрела похож на выстрел из дробовика, а не автомата. Возможно потому, что анимация отдачи и вспышки эффекта не синхронизированы. Пока оставляю как есть.

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

При клике на конкретного персонажа можно показать все детали отдельной панелькой, но в общем ракурсе игроку будет достаточно следующей информации:
- Где мой персонаж (сейчас за это отвечает зеленый бар здоровья)
- Имя (от этого никуда не деться, оставлю)
- За какую сторону играет персонаж (снова цвет бара - синий/красный)
- Насколько он здоров примерно (в процентах)
- Сколько у него энергии (желтый бар - не уверен, что нужно постоянное отображение на поле)

Своего и сторону можно подсветить каким-нить эффектом, низкое здоровье тоже.

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

Очень много копий уже сломал, работая над несчастным отображением информации над персонажами. Все дело в том, что здесь нужно поженить 2д мир с 3д миром, ведь надписи должны быть плоскими, но при этом отображаться над головами персонажей и следовать за ними при передвижениях.

Начинается все просто - 2д компонент цепляется к 3д модели, но на этом простота и заканчивается. Дальше есть два вида рендеринга - screen и world. Screen подразумевает отображение 2д-информации в экранных координатах, и может показаться, что это то, что нужно... Но нет, привязка координат к 3д модели здесь попросту не работает и почти все туториалы рекомендуют использовать рендеринг 2д через симуляцию 3д-объекта, т.е. World. У них это даже нормально работает. Но не в моем случае...
Не в моем случае потому, что и в шутерах от первого лица, и в шутерах от третьего лица, на примере которых сделаны все туториалы, игрок смотрит на происходящее с той же высоты, на которой находятся противники. И пока третья координата камеры не меняется - все ок. Но в моем случае она приподнята над полем боя, и вот здесь все идет не так и я никак не могу найти правильного решения этой проблемы.

В чем заключается идея... рендерить текст как 3д-объект, чтобы он мог следовать за персонажем (по факту привязывать нужно к основной кости персонажа, иначе будут артефакты) и скалироваться в зависимости от удаленности персонажа на поле боя (для больших боев это будет актуально), а чтобы текст в итоге выглядел как 2д-объект - в каждом кадре поворачивать его таким образом, чтобы он всегда смотрел в камеру, тогда он будет выглядеть как плоский, по сути таковым не являясь. Это действительно работает при камере на уровне глаз любого из персонажей, а в моем случае выглядит так, как на скриншоте выше.

Кроме того 3д-объект - есть 3д-объект, по умолчанию он реагирует на освещение, отбрасывает тени и т.п.. Для компенсации всего этого безобразия вам нужно будет сделать кастомный материал, который будет сконфигурен под 2D GUI и будет отключать тени, декали, освещение и прочие фишки из 3д-мира.

Ну и, вишенка на торте - в Unreal Engine очень криво работает наследование на уровне блюпринтов. Вы добавляете 2D-компонент в базовый класс персонажа, ожидаете, что он появится у всех наследников, запускаете игру - и он отображается над тремя из пяти. Идете проверять - компоненты, вроде, есть у всех, но отображаются только у половины. В итоге, как подсказал, ChatGPT, это очень частая "особенность" движка - он может по одному ему известной логике и без явной на то причины оверрайдить настройки компонент в наследниках. В моем случае это и произошло - компонент добавился, но вот его настройки, почему-то прокинулись только в 3 наследника из 5. Таких подстав обычно не ожидаешь и понять что происходит при тоннах галочек и прочих настроек в каждом объекте интерфейса бывает достаточно сложно.
Спустя множество проб и экспериментов, пока остановился на таком варианте. Подытожим:
- Первая строчка - имя персонажа, неотъемлемая часть.
- Ее цвет - оттенки синего (синяя команда), красного (красная команда), зеленого (наш пользователь).
- Под ником чуть мельче отображаем здоровье и энергию в формате текущее/максимальное. Возможно тут, все же, стоит добавить бары, но пока не буду.
- Ключевой момент, который мне подсказал Олег - весь текст имеет черную обводку толщиной в 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), причем вдвое дешевле, но она мне показалась излишне пафосной и слабо применимой к моему случаю.
This media is not supported in your browser
VIEW IN TELEGRAM
Как выглядит работа со сторонними анимациями из стора.

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

Но даже если вам повезло изначально иметь все модели со стандартным скелетом от Unreal Engine (не мой случай, я делаю риг в Mixamo) - расслабляться все равно рано. Дело в том, что в UE4 скелет один (Mannequin), а в UE5 - два: мужской (Manny) и женский (Quinn) (почему-то все время напоминает Фредди Мак и Фенни Мэй, для тех кто в теме). Конечно они разные, в UE5 больше костей для лучшей детализации движений и они разделены по полу, потому что мужская и женская анатомия существенно отличаются. Не сложно догадаться под какой скелет сделано больше всего анимаций в сторах, ведь он старше и универсальнее.

Короче у меня уже солянка из 4х скелетов - Mixamo (основной), кастомный (старые анимации), Manny и Mannequin. И накладываются они друг на друга уже далеко не идеально...
А еще в паке меня ждал вот такой сюрприз... анимации крайне гранулированы.

Т.е. там, где я ожидал одну анимацию выстрела из двух пистолетов - получил 4 (начало атаки, выстрел левой рукой, выстрел правой рукой, окончание атаки). Есть в наборе также просто анимация выстрела с двух рук сразу, но сами посмотрите как она выглядит на видео из предыдущего поста...

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

Но вывод, ребята, здесь один: если есть возможность - лучше начинать проект сразу со своим аниматором в команде, потому что на эти танцы с бубном уходит слишком много времени.
Снова меня догнала необходимость рефакторинга, иначе дальше двигаться будет сложно.
Решил заняться разработкой набора высокоуровневых функций, чтобы с системой боя было работать просто, не задумываясь каждый раз о необходимых анимациях, стойках/атаках под разные типы оружия и т.п..

В результате хочу избавиться от методов типа СтреляйИзВинтовки() и СтреляйСДвухПистолетов() и иметь один метод Атакуй() на все случаи жизни, который будет понимать с каким оружием в руках находится персонаж и запускать необходимые анимации, в т.ч. на цели (попал, увернулся, заблокировал). То же самое касается боевых стоек - при экипировке оружия буду определять его тип и автоматически переключаться на соответствующую анимацию, в т.ч. по завершении действий вроде передвижений, атак, уворотов, уронов, блоков.

Все это займет какое-то время, такие вещи в блюпринтах делать очень неудобно и, как водится, демонстрабельного результата такая работа не принесет, поэтому на следующей неделе разберу немного околотехнических деталей по прошлому ролику для тех, кто подписан на канал для пассивного и неспешного изучения особенностей игростроя в целом, и на UE в частности.
RustyBits | Дневник разработчика
Ну... уже, как будто бы, становимся похожими на игру.
Давайте разберем несколько интересных аспектов того, что было реализовано в этом видео.

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, присвоить обратно ячейке и манипулировать свойствами уже этого конкретного экземпляра материала. Вот такая, не сразу очевидная, но вполне логичная, оптимизация.
Закончу историю материалов функцией мигания ячейки.

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

Как это работает:

- Берем две текстуры (основная белая + желтая с изображением передвижения) и постепенно их смешиваем, используя функцию Lerp (линейная интерполяция), степень смешивания задаем параметром Alpha

- Чтобы не просто морфились текстуры, а также добавлялся эффект свечения - дополнительно делаем Lerp между полной прозрачностью и нужным цветом с той же Alpha и присваиваем результат в Emissive Color материала

- Саму Alpha, чтобы она менялась во времени, нужно зациклить по синусоиде - для этого берем Time (тики, прошедшие с запуска игры), передаем в функцию Sine (сделает из этого числа синусоиду от -1 до 1), к результату добавляем 1 и делим пополам, чтобы конвертировать в синусоиду от 0 до 1, которая и нужна для правильной работы процента смешивания.