This media is not supported in your browser
VIEW IN TELEGRAM
Нет, не получится использовать сделанные под заказ анимации к сожалению, даже не смотря на продвинутый ретаргетинг в Unreal Engine 5. Технически это, конечно, работает, но различия слишком огромны и несовместимы с моими внутренними стандартами качества.
Излишнюю горбатость еще, может быть, можно было бы стерпеть, но плавающие ноги...
Профессиональный аниматор, наверняка, разобрался бы как нужно поправить кастомный скелет и соответствующие анимации для более адекватной конвертации, но в текущей команде (из одного человека :)) такого нет. Придется искать аналоги в Mixamo и сторах.
Излишнюю горбатость еще, может быть, можно было бы стерпеть, но плавающие ноги...
Профессиональный аниматор, наверняка, разобрался бы как нужно поправить кастомный скелет и соответствующие анимации для более адекватной конвертации, но в текущей команде (из одного человека :)) такого нет. Придется искать аналоги в Mixamo и сторах.
This media is not supported in your browser
VIEW IN TELEGRAM
Ретаргетинг в Mixamo из стандартного в UE скелета Manny работает лучше. Но есть нюанс...
Наткнулся на первое ограничение блюпринтов по сравнению с кодом - не поддерживаются двумерные массивы.
Мне нужно сгенеренную боевую сетку выставить наружу для того, чтобы дальше на ней позиционировать бойцов в блюпринтах сцен. Самый очевидный метод - создать двумерный массив, чтобы по координатам типа hex[0,2] можно было получить нужную клетку и дальше с ней работать. Но сделать это в блюпринтах оказалось невозможным.
Традиционно ChatGPT об этом не сказал, настойчиво придумывал сказки о том, как это можно нарулить через интерфейс. Причем он постоянно дает инструкции к UE4, в котором интерфейс IDE сильно отличался, и часто проблема решается уточнением типа "а теперь дай инструкцию к UE5". В этот раз такая директива только усугубила ситуацию - "Конечно, ты прав!" сказал ChatGPT и придумал еще какую-то инструкцию, которая тоже крайне далека от реальности, и на проверку которой я снова потратил время.
В общем пришлось гуглить и там ответ нашелся сразу - это невозможно. На C++ переключаться ради такой ерунды не хотелось, поэтому в голову сразу пришли 3 альтернативы:
1. Использовать одномерный массив, а индекс вычислять из двух координат. Например X*100+Y. Таким образом всегда получаем уникальное число и его можно использовать как индекс в одномерном массиве. Очевидная проблема такого подхода - фрагментация массива и лишнее отжирание памяти, ведь если для карты 8x4 в двумерном массиве нужно будет создать 32 элемента, то при таком подходе - 8*100+4=804, а заняты будут только 32. Лишний расход памяти.
2. Не использовать массив вообще, вместо этого проименовать добавляемые гексы как 0x0, 0x1,... 7x3, потом искать элементы по имени. Очевидный минус такого подхода - сам поиск. Ведь получение элемента массива - это O(1), а поиск по имени - O(n). Получаем лишнюю нагрузку на CPU.
Мне нужно сгенеренную боевую сетку выставить наружу для того, чтобы дальше на ней позиционировать бойцов в блюпринтах сцен. Самый очевидный метод - создать двумерный массив, чтобы по координатам типа hex[0,2] можно было получить нужную клетку и дальше с ней работать. Но сделать это в блюпринтах оказалось невозможным.
Традиционно ChatGPT об этом не сказал, настойчиво придумывал сказки о том, как это можно нарулить через интерфейс. Причем он постоянно дает инструкции к UE4, в котором интерфейс IDE сильно отличался, и часто проблема решается уточнением типа "а теперь дай инструкцию к UE5". В этот раз такая директива только усугубила ситуацию - "Конечно, ты прав!" сказал ChatGPT и придумал еще какую-то инструкцию, которая тоже крайне далека от реальности, и на проверку которой я снова потратил время.
В общем пришлось гуглить и там ответ нашелся сразу - это невозможно. На C++ переключаться ради такой ерунды не хотелось, поэтому в голову сразу пришли 3 альтернативы:
1. Использовать одномерный массив, а индекс вычислять из двух координат. Например X*100+Y. Таким образом всегда получаем уникальное число и его можно использовать как индекс в одномерном массиве. Очевидная проблема такого подхода - фрагментация массива и лишнее отжирание памяти, ведь если для карты 8x4 в двумерном массиве нужно будет создать 32 элемента, то при таком подходе - 8*100+4=804, а заняты будут только 32. Лишний расход памяти.
2. Не использовать массив вообще, вместо этого проименовать добавляемые гексы как 0x0, 0x1,... 7x3, потом искать элементы по имени. Очевидный минус такого подхода - сам поиск. Ведь получение элемента массива - это O(1), а поиск по имени - O(n). Получаем лишнюю нагрузку на CPU.
3. Создать кастомную структуру, единственный элемент которой - одномерный массив, потом создать массив этих кастомных структур и его заполнять. С т.з. производительности это наилучший вариант. С т.з. использования - не очень, потому что если в коде это выглядело бы просто как hexagons[0].arrayItem[2], то в блюпринте получаем целую портянку. Ну да ладно, просто запихну это в отдельный удобный метод.
RustyBits | Дневник разработчика
Наткнулся на первое ограничение блюпринтов по сравнению с кодом - не поддерживаются двумерные массивы. Мне нужно сгенеренную боевую сетку выставить наружу для того, чтобы дальше на ней позиционировать бойцов в блюпринтах сцен. Самый очевидный метод - создать…
В комментариях к посту коллега справедливо заметил, что опция #1 вполне рабочая если для множителя вместо константы (в моем примере 100) использовать максимальную длину массива (в моем случае размер поля по горизонтали). Более того в C двумерные массивы - всего лишь синтаксический сахар, а под капотом лежит одномерный массив, индексы которого так и рассчитываются. Т.е. опция #1 - это фактически и есть реализация двумерного массива в языках, и если об этом подумать - максимально логичная реализация. Век живи - век учись...
И еще один момент по кастомным структурам - это реально структуры, а не классы. Т.е. хранятся в куче и передаются по значению, а не по ссылке. Да, наверное они не просто так называются структурами, но мы уже сталкивались с кейсом Material и Material Instance где терминология не отражает фактического поведения, поэтому этот момент важно уточнить. Причем при использовании кастомных структур возникают странности (от моего незнания движка конечно же) - если создать инстанс структуры, сохранить в переменную, добавить ее (переменную) в массив, а потом что-то изменить в этой переменной - изменение происходит не в том инстансе, который был добавлен в массив, даже не смотря на то, что все манипуляции происходят внутри одного блюпринта. Видимо он где-то под капотом неявно передает переменную как параметр, в результате чего она копируется. Записали, запомнили.
И еще один момент по кастомным структурам - это реально структуры, а не классы. Т.е. хранятся в куче и передаются по значению, а не по ссылке. Да, наверное они не просто так называются структурами, но мы уже сталкивались с кейсом Material и Material Instance где терминология не отражает фактического поведения, поэтому этот момент важно уточнить. Причем при использовании кастомных структур возникают странности (от моего незнания движка конечно же) - если создать инстанс структуры, сохранить в переменную, добавить ее (переменную) в массив, а потом что-то изменить в этой переменной - изменение происходит не в том инстансе, который был добавлен в массив, даже не смотря на то, что все манипуляции происходят внутри одного блюпринта. Видимо он где-то под капотом неявно передает переменную как параметр, в результате чего она копируется. Записали, запомнили.
This media is not supported in your browser
VIEW IN TELEGRAM
Убираем трупы с поля боя через динамический material instance, смешивающий основную текстуру персонажа со стандартной текстурой шума и применение маски непрозрачности (opacity mask).
Я долго думал стоит ли первый бой делать настоящим, т.е. привязанным к серверу и управляемым с него. С одной стороны это хорошо потому, что весь код сразу получится переиспользуемым для других боев, но пришел к выводу, что не стоит, и вот почему.
Во-первых задача первого боя - показать игроку максимальную красоту, чтобы он захотел продолжать. Но он врядли захочет рассматривать все эти анимации и эффекты в последующих боях, возможно вообще захочет их отключить, чтобы проще было сфокусироваться на тактике, убрав все лишнее. Таким образом первый бой должен быть очень эффектным, а последующие - максимально динамичными и практичными.
Во-вторых для красивой картинки может появиться желание показать что-то такое, чего в реальном бое не будет вовсе (потому что не нужно), и это будет гораздо проще сделать отдельно, чтобы проще было изменять в будущем, независимо от механики реального боя.
В-третьих первый бой - это точка отсечки тех игроков, кому не зайдет жанр. Запускаю рекламу - приходит 10,000 игроков, из них 9,000 отваливается. Если первый бой пускать через сервер - он должен выдерживать такие скачки нагрузки, а если не привязывать - все скалируется автоматически, потому что интро целиком и полностью выполняется на устройстве пользователя.
И в-четвертых я все еще изучаю движок и по мере продвижения нахожу более правильные варианты решения своих задач, как это обычно и бывает. Например уже понятно, что для всех персонажей должен быть один переиспользуемый блюпринт, который позволит выполнять все базовые действия (типа взять оружие, пройти на клетку вперед, умереть и т.п.) через методы этого блюпринта. Также уже понятно как можно упростить блюпринт самого интро и т.п.. Всей этой информации у меня не было в начале пути и будет лучше сначала пройти его до конца как получится, собрав все грабли, а потом уже сделать все правильно, но отдельно.
Во-первых задача первого боя - показать игроку максимальную красоту, чтобы он захотел продолжать. Но он врядли захочет рассматривать все эти анимации и эффекты в последующих боях, возможно вообще захочет их отключить, чтобы проще было сфокусироваться на тактике, убрав все лишнее. Таким образом первый бой должен быть очень эффектным, а последующие - максимально динамичными и практичными.
Во-вторых для красивой картинки может появиться желание показать что-то такое, чего в реальном бое не будет вовсе (потому что не нужно), и это будет гораздо проще сделать отдельно, чтобы проще было изменять в будущем, независимо от механики реального боя.
В-третьих первый бой - это точка отсечки тех игроков, кому не зайдет жанр. Запускаю рекламу - приходит 10,000 игроков, из них 9,000 отваливается. Если первый бой пускать через сервер - он должен выдерживать такие скачки нагрузки, а если не привязывать - все скалируется автоматически, потому что интро целиком и полностью выполняется на устройстве пользователя.
И в-четвертых я все еще изучаю движок и по мере продвижения нахожу более правильные варианты решения своих задач, как это обычно и бывает. Например уже понятно, что для всех персонажей должен быть один переиспользуемый блюпринт, который позволит выполнять все базовые действия (типа взять оружие, пройти на клетку вперед, умереть и т.п.) через методы этого блюпринта. Также уже понятно как можно упростить блюпринт самого интро и т.п.. Всей этой информации у меня не было в начале пути и будет лучше сначала пройти его до конца как получится, собрав все грабли, а потом уже сделать все правильно, но отдельно.
Самый желанный результат рефакторинга - чтобы все работало также как до него :). Наконец удалось этого добиться.
Сделал общий блюпринт бойцов, перевел все модели и блюпринт сцены на его использование. Набитые шишки:
1. Конструктор базового класса в наследнике по умолчанию не вызывается, нужно это делать явно.
2. Базовый блюпринт для модели со скелетом имеет смысл создавать прямо из модели, а не просто как Actor. Иначе редактор перестает видеть скелет и аттачить к нему другие модели (в моем случае оружие), даже если к нему потом добавить скелетированный меш с нужным ригом. Наверняка есть способ это поправить, но я его не нашел. ChatGPT традиционно водит за нос, гугл тоже не помог.
3. В очередной раз отъехала привязка оружия к скелету персонажей, из-за смены проперти со скелетом вследствие перехода на базовый класс/блюпринт. Придется заново проделать ненавистную работу по его позиционированию. Чтобы этот раз был последним - оружие тоже перевел на базовый класс и пропишу под каждое параметры позиционирования относительно руки, а добавление оружия персонажу сделаю динамическим.
4. При удалении объекта со сцены, его чайлды не удаляются и в движке нет для этого стандартного метода. Придется написать свой, благо статические классы и методы в UE есть.
5. UE очень часто падает при замене базовых классов объектов, которые уже используются в блюпринтах, при этом оставляя кучу файлов измененными в неконсистентном состоянии. Хорошей идеей будет свой проект сразу пихать в сорс контрол и коммититься часто, потому что откатиться до предыдущего состояния зачастую проще чем пытаться понять что он наворотил в этот раз и исправлять.
6. Иногда просто перезапуск редактора убирает странные артефакты, появившиеся после рефакторинга.
Сделал общий блюпринт бойцов, перевел все модели и блюпринт сцены на его использование. Набитые шишки:
1. Конструктор базового класса в наследнике по умолчанию не вызывается, нужно это делать явно.
2. Базовый блюпринт для модели со скелетом имеет смысл создавать прямо из модели, а не просто как Actor. Иначе редактор перестает видеть скелет и аттачить к нему другие модели (в моем случае оружие), даже если к нему потом добавить скелетированный меш с нужным ригом. Наверняка есть способ это поправить, но я его не нашел. ChatGPT традиционно водит за нос, гугл тоже не помог.
3. В очередной раз отъехала привязка оружия к скелету персонажей, из-за смены проперти со скелетом вследствие перехода на базовый класс/блюпринт. Придется заново проделать ненавистную работу по его позиционированию. Чтобы этот раз был последним - оружие тоже перевел на базовый класс и пропишу под каждое параметры позиционирования относительно руки, а добавление оружия персонажу сделаю динамическим.
4. При удалении объекта со сцены, его чайлды не удаляются и в движке нет для этого стандартного метода. Придется написать свой, благо статические классы и методы в UE есть.
5. UE очень часто падает при замене базовых классов объектов, которые уже используются в блюпринтах, при этом оставляя кучу файлов измененными в неконсистентном состоянии. Хорошей идеей будет свой проект сразу пихать в сорс контрол и коммититься часто, потому что откатиться до предыдущего состояния зачастую проще чем пытаться понять что он наворотил в этот раз и исправлять.
6. Иногда просто перезапуск редактора убирает странные артефакты, появившиеся после рефакторинга.
В своем рассказе о декалях я писал, что они работают по принципу прожектора, соответственно если вы добавили трещины на асфальте с помощью декалей, а потом на их место встал боец - трещины будут на нем, а не на асфальте, потому что прожектор - есть прожектор. Чтобы этого избежать нужно отключить рендеринг декалей на бойцах. Делается это одной галочкой, но я тут, как обычно это и бывает, сделал все по классике... Т.е. рассказал и не сделал.
This media is not supported in your browser
VIEW IN TELEGRAM
Наверное набирать команду нужно будет начинать с аниматора, уж слишком много у меня в этой области проблем... Может кто-то уже делал шутеры и знает к какой кости прикреплять винтовку? Сейчас крепится к правой руке и вот что получается. Если прикреплю к левой - винтовка будет держаться за правую руку и выходить из левой...
Media is too big
VIEW IN TELEGRAM
А вы знали, что эффекты в фильмах тоже делают на Unreal Engine?
Это туториал по созданию эффектов выстрела с летящими пулями и искрами при попадании в цель на примере сцены из Годзиллы. Видео дает не только понимание того, как работает система эффектов на частицах в движках, но и какой картинки можно добиться, когда за дело берется профессионал.
Это туториал по созданию эффектов выстрела с летящими пулями и искрами при попадании в цель на примере сцены из Годзиллы. Видео дает не только понимание того, как работает система эффектов на частицах в движках, но и какой картинки можно добиться, когда за дело берется профессионал.
This media is not supported in your browser
VIEW IN TELEGRAM
Ну вроде пазл сошелся, наконец начинаем бой.
This media is not supported in your browser
VIEW IN TELEGRAM
Какой же плохой была идея делать проект без аниматора...
Ребят, тут мне нужна ваша помощь. Посмотрите этот короткий рендер с телефона в нормальном FPS и напишите:
1. Видите ли вы здесь проблемы
2. Если да - насколько они напряжны для вас, как для игрока.
Ребят, тут мне нужна ваша помощь. Посмотрите этот короткий рендер с телефона в нормальном 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. Обратите внимание на передвижение бойца в конце видео - поворот влево, перемещение, поворот вправо.
Насколько оно реалистично? На мой взгляд - ни насколько... В реальности человек бы просто передвинулся наискосок. Но... у меня нет такой анимации. И нигде нет, ее можно только сделать под заказ.
То, что намутил здесь я - это примерно то, что делали в играх конца 90х и начала 2000х. Да, сейчас уже существует locomotion, о нем я уже рассказывал ранее и, более того, есть locomotion паки, которые можно брать прямо с Mixamo и использовать. Пересмотрел все - ни один не подошел. В основном потому, что во всех них повороты сделаны в состоянии покоя, а мне нужно мало того, что в боевых стойках, причем разных (сравните Альвареса, Ирвинга и ГГ например), так еще и достаточно быстро, чтобы не затягивать анимациями динамику игры. Также существуют технологии хитрого смешивания анимаций, когда, например, верхняя часть движется по логике одной анимации, а нижняя - по другой. Но это уж точно оставлю профессионалам своей области.
Кроме locomotion паков на Mixamo есть куча анимаций поворота, но угадайте какая с ними проблема, кроме того, что 95% из них тоже совершаются из состояния покоя? Они сделаны для поворотов на 45-90-180 градусов. На моем гексагональном поле боя повороты нужны на 60-120-180 градусов, т.е. что-то реально готовое я могу взять только для 180, да и то будут вопросы к стойке.
Что я в итоге сделал...
- Взял единственную анимацию поворота влево на 90 градусов, похожую на основную боевую стойку, из Mixamo.
- Сделал ее отраженную версию через функцию мирроринга, чтобы у меня была также анимация поворота вправо (почему-то именно эта анимация поворота на Mixamo только в одном варианте).
- В блюпринте персонажа написал следующую логику: проигрываем анимацию поворота налево на 70%, после этого морфим ее в анимацию передвижения, по окончании анимации передвижения морфим ее в анимацию поворота вправо, которую также проигрываем на 70% и морфим обратно в боевую стойку.
- Еще одна беда в этом подходе в том, что не смотря на то, что и в анимации поворота, и в анимации передвижения, не используется root motion - анимации поворачивают и двигают основную кость (pelvis - эту проблему тоже разбирал ранее), а значит когда персонаж поворачивается - реальный поворот его объекта на сцене не меняется, как и не меняются его координаты при перемещении персонажа анимацией передвижения. Т.е. в моменты переключения анимаций приходится еще вручную менять координаты меша на сцене, не забыв при этом про DelayUntilNextTick между изменением координат и запуском следующей анимации, чтобы логика успела отработать и мы не увидели прыгающего персонажа в одном кадре..
Короче получился полный колхоз и я удивлен, что на эту, самую кривую анимацию в сцене, никто не обратил никакого внимания.
Радует и то, что даже это явно получилось существенно лучше того, с чего я начинал здесь.
Посмотрел также как этот вопрос решают другие - максимально мелкие планы и максимально быстрые и незаметные анимации поворота, чтобы юзер ничего не успел понять.
О других проблемах и выводах в следующих постах.
Telegram
RustyBits | Дневник разработчика
С первой сценой, в целом, первый блин тоже вышел комом. С визуальной частью конечно, не с технической.
Посудите сами, вот здесь уже реализованы:
- Вход в игру под конкретным игроком
- Взаимодействие с боем, идущем на сервере
- Генерация сетки поля боя
-…
Посудите сами, вот здесь уже реализованы:
- Вход в игру под конкретным игроком
- Взаимодействие с боем, идущем на сервере
- Генерация сетки поля боя
-…