Gamedev Андрей Верещагин
5 054

Создание настоящего вида от первого лица в Unreal Engine 4

С анимированным телом персонажа.

В закладки

Технический художник Фабрик Пике (Fabrice Piquet) из компании Allegorithmic в блоге на сайте Gamasutra рассказал, как в Unreal Engine 4 создать «настоящий» вид от первого лица, при котором камера двигается в соответствии с движением головы персонажа. Мы выбрали главное из материала.

«Настоящий вид от первого лица» предполагает, что камера «закреплена» на анимированном теле персонажа, в отличие от простой «летающей» камеры. Такой подход использовался, например, в The Chronicles of Riddick, Syndicate и Mirror’s Edge.

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

Порой, меши тела видны только игрокам, а полная версия туловища используется для того, чтобы отрисовывать тени (она видна также другим игрокам в мультиплеере, как в последних Call of Duty). Это помогает оптимизации, однако вредит погружению.

Меш полного тела (Full-body Mesh) предполагает, что весь персонаж будет состоять всего из одного меша. При этом камера присоединена к его голове. Её позиция или вращение диктуется движениями тела, их не нужно настраивать вручную. Иерархия класса выглядит так.

PlayerContorller всегда находится выше Character (или Pawn) в Unreal, поэтому тут ничего нового. У персонажа есть меш тела, у которого имеется AnimBlueprint для управления анимациями. Наконец, камера, которая присоединена к мешу в конструкторе. Однако это не всё.

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

Я использовал 10 анимаций, но вы можете сделать и больше, если вам, например, хочется, чтобы персонаж смотрел назад. В моём случае вместе с поворотом головы, поворачивается и тело (как в Mirror’s Edge). Это потребовало создание дополнительных анимаций.

Как только анимации импортированы в Unreal, нужно настроить несколько вещей. Убедитесь, что базовая поза для анимаций названа правильно, чтобы быстро найти её, когда потребуется. Я назвал её «anim_idle_additive_base».

Затем, я открыл другие позы и изменил несколько свойств во вкладке «Additive Settings». Параметр «Additive Anim Type» я установил на «Mesh space», а «Base Pose Type» — на «Selected Animation». Наконец, я загрузил мою анимацию базовой позы в слот ассетов. Все эти действия надо повторить для каждой позы.

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

Мой Aim Offset учитывает два параметра ввода: Pitch и Yaw. Эти значения управляются обновлениями игрового кода.

Обновление анимации

Чтобы анимация обновлялась, вам надо перевести данные, которые вводит игрок, в значения, понятные для Aim Offset. Это делается в три шага:

  • конвертация ввода в значение вращения в классе PlayerController;
  • конвертация вращения, основанного на мире, в локальное значение в классе Character;
  • обновление AnimBlueprint на основе значения локального вращения.

Шаг первый: ввод PlayerController

Когда игрок двигает мышку или стики на геймпаде, я учитываю ввод в классе PlayerController и обновляю вращение контроллера, переписывая функцию UpdateRotation().

Шаг 2: локальное вращение камеры

У моего класса Character есть функция, которая называется «PreUpdateCamera()». Она делает следующее.

Функции CameraProcessYaw() и CameraProcessPitch() конвертируют вращение контроллера в значения локального вращения. Функции выглядят следующим образом.

Шаг третий: обновление AnimBlueprint

Последний шаг самый простой. Я получаю переменную локального вращения и передаю её AnimBlueprint, имеющей Aim Offset.

Как избежать лага

Если вы следуете этому гайду и не знакомы с тем, как работает функция Tick() в Unreal Engine, то вы рискуете столкнуться с особенной проблемой — задержкой на один кадр. Это создаёт определённый дискомфорт во время игры.

Всё происходит от того, что камера обнволяется после того, как будут обработаны данные с прошлого кадра. Если вы быстро перемещаете взгляд в игре, а затем резко останавливаетесь, движение прекратится только со следующим кадром. Таким образом, создаются заметные разрывы, вне зависимости от фреймрейта.

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

Можно заметить, что класс Character обновляется после AnimInstance (это AnimBlueprint). Это значит, что локальное вращение камеры будет обсчитываться только на следующем тике, поэтому AnimBlueprint использует старые значения. Чтобы решить эту проблему, я расположил функцию «PreUpdateCamera()» в конце тика PlayerController. Таким образом, система получает свежие данные о вращении до обновления меша и его анимации.

Воспроизведение анимаций

В таком виде вся система уже должна работать. Однако нам нужно заставить её проигрывать конкретные анимации, применённые к целому делу. В этом помогает AnimMontages. Суть в том, чтобы проиграть анимацию, которая переписывает текущий AnimBlueprint.

В моём случае, я хотел проиграть анимацию подъема через некоторое время после падения.

Этот код относительно прост.

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

Не совсем то, что хотел. Слот анимации находится до узла Aim Offset в AnimBlueprint. Поэтому, если вы смотрите вниз, перед тем, как проигрывается анимация, значение вращения удваивается из-за чего персонаж становится в странную позу и смотрит себе между ног.

Решить эту проблему можно с помощью ресета камеры, когда проигрывается анимация. В нашем случае это не повлечёт за собой никаких последствий, ведь управление и так отнимается у игрока. Чтобы перезапустить положение камеры я добавил несколько строк кода в функцию PreUpdateCamera().

Эти строки обсчитываются в начале функции, до расчёта локального вращения камеры из PlayerControll. Они просто сбрасывают pitch на 0 с помощью функции «RInterpConstantTo()». Результат выглядит так.

#опыт #unreal

{ "author_name": "Андрей Верещагин", "author_type": "editor", "tags": ["unreal","\u043e\u043f\u044b\u0442"], "comments": 28, "likes": 80, "favorites": 88, "is_advertisement": false, "subsite_label": "gamedev", "id": 21795, "is_wide": true }
{ "id": 21795, "author_id": 22254, "diff_limit": 1000, "urls": {"diff":"\/comments\/21795\/get","add":"\/comments\/21795\/add","edit":"\/comments\/edit","remove":"\/admin\/comments\/remove","pin":"\/admin\/comments\/pin","get4edit":"\/comments\/get4edit","complain":"\/comments\/complain","load_more":"\/comments\/loading\/21795"}, "attach_limit": 2, "max_comment_text_length": 5000 }

28 комментариев 28 комм.

Популярные

По порядку

Написать комментарий...

Антон Антонов

35

Классовая иерархия

Усыпите своего внутреннего Маркса, это всего лишь иерархия класса.

Ответить

Илья Гутовский

26

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

Ответить

Andrey Apanasik

15

За вставку кода картинками (при условии, что в оригинале нормально всё) нужно бить по рукам.

Ответить

Никита Порутчик

Andrey
3

Оно просто вот так копируется, да и в целом, здешний редактор для кода не предназначен.

Ответить

Danylo Rudenko

Никита
8

Реквестирую фичу!

Ответить

Key Don

Roman
0

Не, лучше пусть кратоса в углу добавят. Целевой аудитории это нужнее!

Ответить

Danylo Rudenko

6

Нам тут еще уроков по Юнити не хватало!

(шучу, у меня просто личная неприязнь к юнити)

Ответить

Сергей Зарницкий

Danylo
1

Если б не Unity, то многих отличных игр могло бы потенциально и не быть.

Ответить

Key Don

Сергей
1

И наоборот - если бы не юнити, то многие отличные игры могли быть еще лучше.

Ответить

Danylo Rudenko

Сергей
0

Читайте внимательно, пожалуйста: "ЛИЧНАЯ неприязнь"

Ответить

Сергей Бровков

Danylo
1

Ты не одинок _/

Ответить

Евгений Войтов

Danylo
0

Возможно, нас таких хватит на отдельный хаб

Ответить

Vitaly Khristoforov

2

Надо чучуть еще анимации добавить ииии...
Вот так идеально

Ответить

Mr. Piccolo

1

"Такой подход использовался, например, в The Chronicles of Riddick, Syndicate и Mirror’s Edge."

И ещё в 3-ем Воре, например

Ответить

Анон Анонов

Mr.
1

И в фростбайтных онлайн шутерах: бф 3,4(1,5?) св бф

Ответить

Herr Ulitochka

Mr.
0

И в R6: Siege

Ответить

Mr. Piccolo

Herr
0

А вас не смутило что указанные выше примеры относятся к нулевому десятилетию? (ну, кроме синдиката) А вора я указал в качестве раннего экземпляра

Ответить

Herr Ulitochka

Mr.
1

Синдикат вышел в том же десятилетии, что и Осада.

Ответить

Александр Болуженков

1

Не понятно чем это помогает погружению. И делать придется все равно 2 набора анимаций. И 2 меша у рук, как минимум если нужен вид от 3го или есть другие игроки
Анимации которые выглядят хорошо для 1 лица - часто выглядят отвратно от 3го. в Mirrors Edge можно было через "чит" включить камеру от 3го, выглядело мега паршиво.
Меши для 1 лица гораздо более детальные должны быть.
И да, тряска обычно очень сильная для камеры, ее нужно хитро "гасить" если следовать такому подходу.

Но если у нас сингл от 1-го лица - то конечно проблем нет

Ответить

Alexey Prilovsky

0

так а чего это не в Gamedev?

Ответить

Alexey Prilovsky

Alexey
2

а, теперь это подсайт же. привык к тому, что шапка окрашивалась в жёлтый

Ответить

Maxim Chudaev

0

Отличная статья, спасибо! Но возникает вопрос про тряску камеры во время передвижения. Дело в том, что некоторые игроки склонны испытывать головокружение и тошноту при подобной тряске. К примеру, в Virginia играть почти невозможно — мутит ужасно. Планируете добавить какую-то амортизацию? Расскажите?

Ответить

Alex GrimT

Maxim
1

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

Ответить

Роман Кучма

Alex
0

А пробовали к сокету головы цеплять оочень мягкий SpringArm с камерой?

Ответить

Голубев Ярослав

Maxim
0

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

Ответить

Eugene Lychany

0

Какой Фабрик, Фабрис же

Ответить

Голубев Ярослав

0

Момент с падением - понравился. Но вот направление движения головы.... Отдает крипотой.

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

Ответить
0

Прямой эфир

Подписаться на push-уведомления
[ { "id": 1, "label": "100%×150_Branding_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox_method": "createAdaptive", "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfl" } } }, { "id": 2, "label": "1200х400", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfn" } } }, { "id": 3, "label": "240х200 _ТГБ_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fizc" } } }, { "id": 4, "label": "240х200_mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "flbq" } } }, { "id": 5, "label": "300x500_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfk" } } }, { "id": 6, "label": "1180х250_Interpool_баннер над комментариями_Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "clmf", "p2": "ffyh" } } }, { "id": 7, "label": "Article Footer 100%_desktop_mobile", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjxb" } } }, { "id": 8, "label": "Fullscreen Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjoh" } } }, { "id": 9, "label": "Fullscreen Mobile", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjog" } } }, { "id": 10, "label": "Native Partner Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyb" } } }, { "id": 11, "label": "Native Partner Mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyc" } } }, { "id": 12, "label": "Кнопка в шапке", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fdhx" } } }, { "id": 13, "label": "DM InPage Video PartnerCode", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox_method": "createAdaptive", "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "clmf", "p2": "flvn" } } }, { "id": 14, "label": "Yandex context video banner", "provider": "yandex", "yandex": { "block_id": "VI-250597-0", "render_to": "inpage_VI-250597-0-1134314964", "adfox_url": "//ads.adfox.ru/228129/getCode?pp=h&ps=clmf&p2=fpjw&puid1=&puid2=&puid3=&puid4=&puid8=&puid9=&puid10=&puid21=&puid22=&puid31=&puid32=&puid33=&fmt=1&dl={REFERER}&pr=" } }, { "id": 15, "label": "Плашка на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byudo", "p2": "ftjf" } } }, { "id": 17, "label": "Stratum Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fzvb" } } }, { "id": 18, "label": "Stratum Mobile", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fzvc" } } } ]