Gamedev Антон Антонов
3 839

Создание процедурной диалоговой анимации в Unreal Engine 4

В закладки

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

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

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

Кроме того, при указании типов и имен классов я использую синтаксис C++. Например, вместо Actor Component я пишу UActorComponent. Так, согласитесь, понятнее.

Создание диалогов

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

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

  • ID персонажа;

  • ID реплики;
  • ID ответных реплик;
  • ID звукового ассета (ID фразы);
  • список условий, делающих реплику доступной;
  • список событий, которые необходимо вызвать при произнесении этой реплики;
  • параметры активации;
  • всякие дополнительные управляющие параметры.

Если вы не очень знакомы с базами данных, небольшое пояснение лишним не будет. Наша задача — уместить «дерево» диалога в плоской таблице. Обычно дерево вписывается таблицу путём добавления к каждой строке ссылки на родительскую строку. В нашем случае требуется не от ветки спускаться к корню, а, наоборот, от корней двигаться к листьям (от начала диалога — дойти до любого из возможных финалов), поэтому в каждой строчке хранится не идентификатор родительского элемента (строки, на которую этот элемент отвечает), а идентификатор строк-ответов.

Если вам всё понятно, спускайтесь ниже, если нет — смотрите пример:

Теперь разберёмся с дополнительными столбцами таблицы. Из всего списка, пояснения требуются для трех пунктов. Первый — идентификатор звукового ассета. Можно напрямую использовать указатель на звуковой файл, но я настоятельно советую вынести информацию о звуке в отдельную таблицу. Причина для этого проста: в теории, никто не запрещает использовать одну фразу (скажем, Yes, или No или I should go) в множестве диалогов. Если информацию о фразах хранить прямо в диалогах, то придётся многократно дублировать субтитры и другие параметры. А это противоречит какому-то принципу построения баз данных, и вообще неправильно.

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

Второе, что надо пояснить — это параметры активации. То, каким образом игрок (если это не реплика NPC, конечно) сможет выбрать ответ. Сюда входит тип активации (простая текстовая ссылка, или клавиша быстрого действия, или что-то оригинальное, что придет в голову вашему геймдизайнеру), здесь же должно быть краткое описание реплики, если вы не планируете показывать фразу игроку целиком, расположение реплики (номер пункта в списке, в каком месте на «колесе» ее рисовать) и тому подобное.

Наконец, остаются «управляющие параметры». Под этими словами я имею в виду набор флагов, которые позволят более гибко использовать таблицу. Приводит ли данное высказывание к завершению разговора? Или, может, это лишь часть фразы, и после ее завершения нужно продолжить речь со следующего пункта, а не передавать слово собеседнику? Отдельное поле на каждый флаг делать не обязательно, флаги можно хранить в одном числе и считывать по битовой маске. Ну или использовать текстовое поле с кодовыми словами, мы же не в 1990 году живём. При создании сложных диалогов с такими тонкостями всё равно придётся столкнуться, поэтому лучше предусмотреть их заранее.

Например, кто захочет при заказе озвучки переплачивать за двойное чтение фразы, которая отличается лишь окончанием? Проще разделить её на единое начало и две реплики-окончания, которые будут автоматически воспроизводиться после основной фразы в зависимости от каких-то условий.

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

  • ID фразы;

  • указатель на звуковой файл;
  • субтитры; в Unreal это обязательно будет поле типа FText (а не, например, FString) для подключения к встроенным средствам локализации;
  • параметры анимации.

Последний пункт требует пояснений, но их до второй главы не будет. С остальными, как мне кажется, всё ясно.

Итак, на этом этапе мы определились, что потребуется записать в движок, и как должна быть организована информация. В одной таблице у нас — перечень звуковых файлов с субтитрами и параметрами анимации персонажа. В другой — «дерево» диалоговых реплик. Теперь надо понять, как загрузить это барахлишко в Unreal.

Для этих целей в UE4 есть много средств. Есть даже довольно популярный плагин, позволяющий заполнять диалоги в виде дерева прямо внутри движка. Я от этого варианта решил отказаться, поскольку при создании больших диалогов преимущество наглядности будет перевешиваться недостатком громоздкости. Более простой вариант, на котором я и остановился — использование таблиц данных (Data Table). По своей сути они полностью соответствуют названию, то есть позволяют хранить таблицы с текстовым (FName) ключевым полем, где каждому значению ключа соответствует одна строка таблицы. Проще говоря, они аналогичны контейнерам std::map<std::string, struct_name> в STL или TMap<FName, FStructName> в UE4.

Проблема заключается в том, что нам такой формат не совсем подходит. На каждую реплику NPC у игрока может быть несколько вариантов ответа, а значит, одному ключу может соответствовать несколько строк. Нам нужна, прости господи за перевод, мультикарта. Имитировать её в Data Table не сложно. Достаточно добавлять к ключу номер реплики, наподобие _1, _2 и так далее. Но потом придётся не хранить диалоговую информацию прямо в таблице, а выгрузить в TMultiMap.

К сожалению, в блюпринтах контейнер TMultiMap недоступен. Также следует иметь в виду, что он не поддерживает сетевую репликацию. Так что, если не хотите использовать С++, для поиска ответа с кодом «УГУГУ» вам придётся в цикле проверять ключи «УГУГУ_1», «УГУГУ_2» и так далее, пока поиск не перестанет давать положительный результат.

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

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

Импортировать таблицы в UE4 можно из форматов JSON или CSV, а заполнять их удобнее всего в Excel. Да, занятие это не из приятных, поэтому лучше засадить за него самого нелюбимого сотрудника. Лично я для себя разработал такую схему наименования ключей реплик: в качестве префикса используется код игрока, затем следуют 1-3 значимых слова из начала фразы, на которую отвечает персонаж. Например, так будет выглядеть диалог игрока Васи и вольного торговца Пети (другое время, другое место, другая вселенная).

Как вы понимаете, идентификаторы строк совсем не обязательно должны быть понятными для человека. Их создание вполне можно автоматизировать, что избавит вас от половины работы.

Excel умеет экспортировать файлы в CSV, но, если вы захотите таким способом перенести в UE4 массивы (а вы захотите), у вас возникнут проблемы. Лучше написать небольшой макрос, который по нажатию кнопки выгрузит таблицу из Excel в JSON. Именно так я и поступил. И для фраз, и для реплик.

Обработка диалогов у NPC и PC отличается, но эти различия не так уж велики. Когда собеседник завершает свою реплику, для игрового персонажа нужно отобразить возможные варианты ответа, а для NPC — выбрать один вариант и сразу его воспроизвести. Воспроизведение тоже может отличаться, если у вас игра от первого лица, а не от третьего (в первом случае не нужно воспроизводить анимацию на игровом персонаже). Поэтому обработку диалогов лучше вынести в один универсальный компонент (наследующий от UActorComponent), который можно будет подключать и к NPC, и к PC.

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

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

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

Условия позволяют проверить, доступна ли соответствующая реплика для активации в диалоге. Организовать их можно как угодно. Я, например, использую не двоичную, а числовую систему, то есть предполагается, что параметры состояния персонажа и мира могут принимать значения от 0 до 255. Соответственно, условия доступности реплик выглядят как «Параметр1 > 0» или «Параметр2 < 1» или «Параметр3 == 10».

Для записи параметров состояния мира можно использовать систему геймплейных тегов (FGameplayTag), но в этом нет большой необходимости. Реализовать аналогичную систему можно и на простых текстовых ключах, если вам так удобнее. Однако теги очень хорошо помогают систематизировать состояние мира, и эту систему Epic Games намерена развивать.

Для примера, пусть состояние игрока игрового мира в процессе прохождения выглядит так:

State.Petya.JoinedParty = 1

State. Petya.IsDead = 0

State.World.VillagesSaved = 3

Тогда условием, делающим реплику в диалоге доступной, будет: State.Petya.JoinedParty == 1

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

Система анимации

Создание таблиц диалогов — достаточно простая часть работы. Более того, зачастую у разработчиков нет необходимости в многовариантных диалоговых деревьях. А вот диалоговая анимация персонажей потребуется всем. Для её создания есть два кардинально разных подхода.

Первый — запись (motion capture aka mocap), либо создание по ключевым кадрам отдельных анимаций для каждой фразы и каждой сцены. Это подходит либо для игр с небольшим количеством разговоров, либо для очень крупнобюджетных проектов с умеренным количеством кат-сцен. Даже если у вас бюджет в $100 млн, 20-30 часов диалогов в ручном режиме вы не потянете.

Второй подход — процедурная анимация. Именно этот вариант нас и интересует. Анимацию губ (lip-sync) можно генерировать автоматически в реальном времени, но с жестикуляцией и мимикой это не прокатит. То есть, попробовать, конечно, можно, но либо получится Mass Effect Andromeda, либо вам придётся полноценно заняться разработкой технологии, которая потом сама по себе прокормит вас, и заморачиваться с созданием игры даже не придётся.

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

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

Морфинг — это линейная интерполяция вершин модели между двумя или несколькими положениями. Давайте возьмем нейтральное лицо. Затем сделаем копию, у которой растянем рот в улыбке. При создании морфинга компьютер посчитает смещение каждой вершины модели головы между «нейтральным» вариантом» и «улыбающимся», после чего мы сможем плавно смешивать два этих выражения, сдвигая вершины на 10%, на 50% или на 90%. Или на 200% — просто в два раза дальше в том же направлении. Почему нет? Получится, конечно, страшный ужас.

Если говорить о создании лицевой анимации, то разница между морфингом и костями описывается так: при смешивании нескольких морфов (Morph Targets, или Blend Spaces в терминологии Maya) изменения на модели суммируются. А вот при смешивании нескольких анимаций костей мы получаем усредненное значение.

Более понятно станет, если привести пример. Возьмём лицо, произносящее букву «О», и лицо с эмоцией удивления (рот открыт, брови подняты). При наложении двух таких морфов рот будет растянут вдвойне: и от буквы «О», и от удивленного лица. А теперь представим два пресета, записанные на кости: опять буква «О» и хмурое лицо с закрытым ртом. Прямое смешивание тоже не даст хорошего результата: положение рта будет средним (наполовину открыт), брови тоже будут нахмурены лишь наполовину (ведь в анимации «О» они не нахмурены вовсе).

Здесь слишком много морфов накладывается друг на друга

К счастью, анимацию не обязательно смешивать целиком. Можно наложить один пресет на кости рта, а второй — на кости бровей и щек. Тогда и рот, и нахмуренные брови будут на месте. Но в некоторых случаях на стыке частей лица, анимированных отдельно (то есть на стыке рта и щёк), могут возникать артефакты. Проблемы можно обойти и с морфингом. Для этого потребуется два набора морфов эмоций: для молчащего персонажа и для говорящего. Последние не должны применять сильных искажений к вершинам модели вокруг рта.

Что лучше выбрать? Это зависит только от ваших целей, возможностей и личных предпочтений.

Простите

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

Существует софт для генерации «голов» с готовым морфингом (FaceGen), либо можно взять стандартную голову с морфингом (Daz3D) и перескульптить её в ZBrush (да, это законно, если вы не забудете поменять текстуры). Морфинг может показать вполне достойный результат, если вы не претендуете на создание AAA-игры.

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

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

Root Motion — это подход, при котором анимация сама двигает актор персонажа, а не подстраивается под двигающийся актор. Так выглядит классический подход: пользователь нажимает клавишу W, персонаж начинает двигаться, а 3D-модель начинает проигрывать анимацию с нужной скоростью, чтобы симулировать бег по поверхности.

А так выглядит подход с Root Motion: пользователь нажимает кнопку, и 3D-модель начинает проигрывать анимацию бега, в которой смещение рутовой кости будет двигать актор вперед. В данном случае Root Motion необходим, потому что персонаж, сделавший во время разговора шаг назад, должен после окончания анимации остаться в этой позиции. То есть при воспроизведении анимации сдвинуться должна не только 3D-модель, но и весь актор.

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

Осталось сказать пару слов о липсинке. Самый распространенный инструмент для создания анимации губ в реальном времени — FaceFX. За эту программу придётся отдать 900 долларов, а при хороших продажах ещё и добавить роялти. FaceFX — основной инструмент на рынке, который применяется в большинстве AAA-игр. Если вас не устраивают условия, то можете при помощи сторонних программ (кажется, iClone это может?) автоматически наделать и сохранить анимационные ассеты с липсинком для всех своих фраз.

Теперь вернёмся к «таблице фраз», о которой говорилось в самом начале. В первом разделе я не объяснил, что такое «параметры анимации». В полном виде структура этой таблицы у меня выглядит таким образом:

  • ID фразы;
  • указатель на звуковой ассет (помните, что встроенная локализация в UE4 применяется к UDialogueWave/UDialogueVoice, но не USoundWave);
  • субтитры;

  • тег постоянной анимации туловища;
  • тег постоянного выражения лица;
  • дополнительные анимации. Массив [отметка времени — тег эмоции].

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

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

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

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

Для анимаций, которые должны проигрываться единовременно, в UE4 применяются слоты. Их обычно используют для анимаций атак, но для проигрывания жестикуляции и коротких эмоциональных анимаций они тоже подходят. Нам потребуется создать четыре отдельных слота для лица, головы, верхней половины туловища и всего персонажа (FaceSlot, HeadSlot, UpperBodySlot, FullBodySlot). Имейте в виду, что слоты в одной группе не могут быть активны в одно время. Если разместить в одной группе слоты головы и лица, то новая анимация головы прервет начавшуюся раннее лицевую анимацию.

Так выглядит компоновка базовой анимации с наложением всех созданных слотов

В стейт-машине (State Machine), которая на картинке выше имеет название Locomotion, тоже потребуется внести кое-какие изменения. Нам потребуется как минимум две зацикленные анимации для состояния разговора: молчащий персонаж и говорящий персонаж. Отличаются они наличием лёгких движений головы и более активными движениями рук при разговоре. Это важно: слишком разные анимации приведут к некрасивым переходам в начале и конце каждой фразы.

Когда персонаж вступает в беседу, он должен перейти на воспроизведение разговорных анимаций (на картинке выше это переход из состояния Idle в состояние Talking). Когда игрок начнет произносить любую фразу, надо начать проигрывать «говорящую» анимацию, при окончании фразы — возвращаться к «молчащей». Если персонаж может разговаривать во время ходьбы, то нужно оставить стандартную анимацию движения на период произнесения фразы и блендить к ней говорящую голову начиная с шеи. Жестикуляцию при ходьбе лучше игнорировать вовсе. Анимации всего тела, разумеется, тоже.

А вот и результат наших трудов. За орную озвучку следует благодарить голосовое API «Яндекса» (на самом деле все не так уж плохо; Google синтезирует английскую речь не лучше, чем «Яндекс» — русскую).

Вася белый, Петя синий. Я бы даже сказал, светло-синий

#опыт

Материал дополнен редакцией

Материал опубликован пользователем. Нажмите кнопку «Написать», чтобы поделиться мнением или рассказать о своём проекте.

Написать
{ "author_name": "Антон Антонов", "author_type": "self", "tags": ["\u043e\u043f\u044b\u0442"], "comments": 24, "likes": 89, "favorites": 93, "is_advertisement": false, "subsite_label": "gamedev", "id": 23804, "is_wide": false, "is_ugc": true, "date": "Wed, 25 Jul 2018 18:58:33 +0300" }
{ "id": 23804, "author_id": 4617, "diff_limit": 1000, "urls": {"diff":"\/comments\/23804\/get","add":"\/comments\/23804\/add","edit":"\/comments\/edit","remove":"\/admin\/comments\/remove","pin":"\/admin\/comments\/pin","get4edit":"\/comments\/get4edit","complain":"\/comments\/complain","load_more":"\/comments\/loading\/23804"}, "attach_limit": 2, "max_comment_text_length": 5000, "subsite_id": 64954, "possessions": [] }

24 комментария 24 комм.

Популярные

По порядку

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

Приятно видеть такие статьи

Ответить
1

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

Ответить
0

Для каждого _типа условия_ - нужный отдельный блюпринт. Опечатался.

Дерево это единственный способ визуально репрезентировать диалог. Даже левые проги, типа articy:draft делают так. Никто не делает через таблицы.

http://cdn.akamai.steamstatic.com/steam/apps/388600/ss_184e932dc5919cc5ed065a0692c162069f1e82ba.jpg

Ответить
0

Ты бы ледок приложил что ли, сынок.

Это модульность и это лучшее решение из вообразимых _вообще_

Нет, это гигантская куча лишних классов на пустом месте. К тому же, этот плагин нарушает принцип нормализации БД. Это просто неправильно с точки зрения архитектуры.

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

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

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

Странно. У меня в экселе текст выбирается из выпадающего списка.

Дерево это единственный способ визуально репрезентировать диалог. Даже левые проги, типа articy:draft делают так.

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

Никто не делает через таблицы.

Помимо плагина Спартана на Маркетплейсе куча плагинов, работающих через таблицы.

Статья - результат 2,5 недель изысканий. Я решил, что организовать большой объем информации в таблице будет удобнее, чем в огромном дереве. Обсидиан делает деревьями? Ну, видимо, я выбрал неправильно, у них-то побольше опыта. Значит ли это, что у меня в статье где-то есть ошибки? Да ни на миллиметр. Первая часть статьи - теоритическая. Она в теории на более низком уровне объясняет то, что софт на практике реализует на более высоком уровне. Она дана для общей информации, основная часть статьи - вторая. Прочитай заголовок, сынок.

Ответить
0

сынок

сынок

возгорелся

Упс, я думал ты в состоянии нормально общаться, т.к. статью написал вроде без скатывания в истерики.

Из:
1) https://leonardo-direct.osnova.io/3bf40d6d-57d2-9314-4bba-134fd8eaf418/resize/1400/ или
2) https://cdn1.epicgames.com/ue/product/Screenshot/de02-1920x1080-8db670e3753f2d37924b6025c3fae3fd.jpg
Я думаю, все сами смогут выбрать, с чем им проще работать.

Ответить
0

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

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

Ответить
0

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

Ответить
0

Например, в ноудах писались диалоги Pillars of Eternity, Wasteland 2, Torment: Tides of Numenera, Age of Decadence, во всех РПГ новой волны. Вот скрин из эдитора диалогов Пилларсов (их внутристудийная тулза) https://s3.amazonaws.com/ksr/assets/003/201/363/cef697007e5c8316acd3a4eeaa854266_large.png?1422477160

Сейчас на ноудах пишутся диалоги в разрабатываемой игре Обсидианов (тех. название Project Louisiana), в разрабатываемой игре Iron Tower Studios (тех. название Colony Ship RPG). Есть скрины из их эдиторов.

Ответить
0

Ха-ха-ха-ха-ха, всё, понял. Ты и есть Спартан. То-то так возгорелся из-за своего плагина.

Ответить
2

Зачем все это, если есть
Переспросить 

[САРКАЗМ] ◄    ►Нет
   ▼
Да 

Ответить
4

Я тебе починил)

          Переспросить 
              ▲
[САРКАЗМ]  ◄      ► Нет
              ▼
              Да 

Ответить
1

Я тебе починил :)

          Переспросить 
              ▲
[САРКАЗМ]  ◄    ► Нет
              ▼
              Да 

Ответить
0

"А вот при смешивании нескольких анимаций костей мы получаем усредненное значение."
А разве нет силы смешивания таких анимаций?

Ответить
3

Да, т.е. степень смешения анимаций при блендинге можно регулировать. Но это в любом случае будет 90% одной анимации + 10% другой или 50/50, или 20/80. Т.е. 100% в сумме. А морфинг складывается. Морф А + Морф Б = 200% смещения вертексов.

Вот понятный пример.

Если морф 1 смещает вершину на 10 см вперед, а морф 2 смещает на 20 см вперед, то при задании морфу 1 значения Alpha1, а морфу 2 значения Alpha2 мы получим смещение вершины на 10 * Alpha1 + 20 * Alpha2. Если Alpha1 = Alpha2 = 100%, то вершина сдвинется на 30 см.

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

Ответить
0

Спасибо, я понял.
А с umg виджетами ты на столько же хорошо разбираешься?

Ответить
1

Озвучка как у Фаргуса.

Ответить
0

Блять, даже этот видос в конце лучше сделан чем анимация в Mass Effect: Andromeda!

Ответить
0

Антон, спасибо за текст! Поправили, чтобы в соцсети вывести.

Ответить
0

Стилистика диалогов хромает (Вася выбивается из образа!), а вот статья полезная и интересная.

Ответить
0

Спасибо за статью.

Ответить
0

Статья топ. Огромное спасибо. просто гигантское.

Ответить
0

Прямой эфир

[ { "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" } } } ]
Уве Болл вернулся в кино
и начал экранизировать flash-игры
Подписаться на push-уведомления