В этой серии статей я расскажу о том, как и почему мы решили создать своё собственное решение для импорта flash-анимаций в Unity, об оптимизациях и внутренней кухне получившегося плагина. А также в программе: рассказ о внутренностях формата swf, особенностях расширения Unity-редактора и вообще об анимациях в целом. Прошу под кат!
Введение
В начале любого проекта стоят муки выбора технологий для многих его частей. Одна из таких частей — система анимаций. Есть несколько переменных от которых этот выбор зависит. Во-первых, в чём привыкли (могут) работать ваши аниматоры или на какие инструменты этих аниматоров вам проще найти. Понятно, что крайне специфичный продукт выбирать не имеет смысла, так как в случае ухода текущих сотрудников, по тем или иным причинам (внезапный автобус?), заменить их будет крайне сложно, часть проекта просто остановится на неопределённый срок, что, в случае коммерческого продукта, может стоить очень дорого. Вторая переменная более техническая: интеграция в ваш движок. Имеются ли сторонние решения и качество этих решений, есть ли силы и возможность создавать своё, а также производительность и удобство всего вышеперечисленного. Ну и третья переменная — это возможности этого инструмента, так как если вам, кровь из носу, нужна инверсная кинематика, то совершенно странно будет выбирать не скелетную анимацию.
Виды 2d-анимаций
Рассмотрим наиболее популярные варианты реализаций анимации в играх. Буду говорить только о 2d-анимациях, 3d-анимации — это тема совершенно другого разговора, подходов и инструментов. Раздел можно без опаски пропускать, если вы подкованы в этих делах (откровений и никаких срывов покровов не будет), либо если вы прицельно зашли посмотреть особенности только flash-анимаций, они ниже.
Покадровые анимации
Открывает наш мини-обзор самый простой и древнейший тип анимаций: покадровая анимация. Каждый кадр такой анимации представлен отдельной картинкой, при быстрой смене которой, возникает иллюзия движения.
Плюсы:
Элементарная реализация, обычно есть в любом движке. Любая сложность и стиль анимаций, хоть фильм по кадрам раскладывай и вставляй (в теории, конечно). Любые инструменты: почти всё что угодно может выдать вам последовательность кадров, которую можно, при необходимости, склеить в один текстурный атлас.
Минусы:
Главный минус — это, конечно, размер этого добра в памяти. Естественно, есть всяческие ухищрения типа нарезки кадров блоками и последующего переиспользования повторяющихся блоков, фоновой загрузки кадров с диска и освобождения уже показанных, и другие. Но и эти ухищрения имеют свои минусы. Нарезка блоками — подходит только для пиксель-арта, фоновая загрузка — создаёт дополнительную нагрузку на диск, занимает много места на этом самом диске, соответственно растёт и размер вашей игры, а также количество необходимых скачиваемых данных для её начала или обновления.
Итого:
Подходит только для маленьких по размеру и количеству кадров анимаций, в идеале для пиксель-арта и стилю ретро-игр а-ля NES.
Видео-анимации
Как апофеоз безумия покадровых анимаций выступают видео-анимации. И да, это используется и даже временами к месту. Мы такое реализовывали для игр в жанре "Hidden Object" для больших и сложных анимаций, а также для кат-сцен с налётом реализма. С помощью кодеков вроде theora и vp8 можно реализовать вполне сносные видео-анимации (и даже с альфа-каналом) своими руками.
Плюсы тут, конечно, в том, что показать в таких анимациях можно всё что душа пожелает, от срежиссированных сцен с участием реальных актёров, до отрендеренных 3d-баталий. Минусами же выступают дикая нагрузка на CPU при декодировании этого и, конечно, качество картинки. Найти компромисс между качеством и производительностью можно в сильно ограниченных и специфичных случаях, как в выше обозначенных Hidden Object играх, например.
В общем, подойдёт далеко не всем и далеко не всегда, вернее почти никогда, если мы не рассматриваем отрендеренные кат-сцены — где таким "анимациям" и место. Хорошая реализация таких анимаций простотой тоже похвастаться не может, много нюансов и трюков, как по качеству выдаваемой картинки, так и по получаемым нагрузкам на центральный процессор. Ввиду специфичности, готовых реализаций мало, качеством они тоже не отличаются, либо стоят бешеных денег (привет, Bink_Video), так что многие пишут свои.
Скелетные анимации
Вот и добрались до популярных и современных видов 2d-анимаций. Скелетные анимации всё больше и больше захватывает сердца и умы разработчиков в сфере 2d, становясь уже стандартом анимации для игр. Забавно, что так широко они пришли в 2d относительно недавно, в отличии от 3d, где они появились во времена Царя Гороха, когда отцы писали Half-Life. Суть скелетных анимаций, как ни странно, в скелете, который создаёт аниматор из костей, связанных друг с другом древовидной структурой. К костям этим привязываются фрагменты в виде отдельных картинок. В движение вся конструкция приводится смещениями и поворотами костей относительно друг друга. Смещения и повороты костей в скелете следуют общему таймлайну анимации.
Плюсы очевидны: нам не нужно хранить каждый кадр анимации в виде отдельных картинок, только фрагменты (ручки и ножки персонажа, например), которые мы и будем двигать костями скелета. Есть прекрасные реализации данного типа анимаций с рантаймами под нужные платформы и движки, с умеренной ценой использования или вовсе бесплатные: Spine, Spriter, Anima2D, DragonBones и другие. Благодаря скелету и его особенностям можно достигать колоссальной плавности анимаций с помощью интерполяции положений его костей, а также смешивать переходы из анимации в анимацию и даже смешивать две разные анимации: стрельба на бегу, стрельба крадясь и так далее. Не буду перечислять все возможности скелетной анимации, их действительно много и они очень крутые. Приведу лучше ссылочку где они наглядно показаны с картинками и описаниями на сайте Spine'а.
Выглядит как серебряная пуля, но нет. Без минусов не обошлось. Сразу оговорюсь, что для многих проектов и анимаций — эти минусы могут не существовать. Если вам подходят скелетные анимации — это великолепно, используйте их, они прекрасны и современны.
Вернёмся к минусам. Дополнительный этап создания анимации — риг (или риггинг, как и многие термины в нашей индустрии, русифицировали как умели). То самое создание скелета и привязка костей к фрагментам. Если у объекта одна анимация, а не несколько, как, например, у персонажа игры в жанре "Platformer", то этап совершенно лишний. Не все анимации удобно анимировать костями. Существует множество анимаций, где кости мешают и без них удобнее, а также быстрее, как в плане создания, так и в плане производительности игры. Отдельным пунктом вынесу специалистов на рынке. Их количество с ростом популярности скелетной анимации растёт, но всё ещё не самое простое занятие найти их себе в команду. Сказывается также разброс средств и инструментов, кто-то привык и использует одно, кто-то другое, необходимо переучиваться на ходу, что создаёт дополнительные трудности в поиске и обучении сотрудников.
Таймлайн-анимации
Этот тип анимаций я вынес в самый конец, не потому, что он лучше всех остальных, а потому, что это как раз те самые flash-анимации о внутренностях которых и пойдёт речь в технической части статьи. Flash — не единственный представитель данного типа анимаций, но безусловно их лидер, так что всё будет описано относительно него. Таймлайн-анимации состоят из фрагментов, которые анимируются отдельными слоями на таймлайне. Почти тоже самое, что мы видели в скелетной анимации, но без скелета. По ключевым кадрам на таймлайне мы можем двигать, поворачивать, заменять, дорисовывать новые фрагменты в эти кадры и, конечно, интерполировать переносы, масштабирования и повороты между ними. То есть, двигаем не кости с привязанными фрагментами, а сами фрагменты. Конечно, мы лишаемся многих возможностей, которые дают нам скелетные анимации, но не всем они и нужны. Взамен же приобретаем другие, такие как:
- возможность дорисовывать и вставлять новые фрагменты в середину анимации;
- нет рига, нет костей, которые временами только мешают;
- Adobe Flash (ныне Adobe Animate) старейший и проверенный годами инструмент для анимаций, очень большое количество аниматоров, так или иначе, владеют им;
- возможность использовать старые анимации при переводе своих flash-проектов на Unity, в том числе и анимаций выполненных в векторной графике, а не только в растровой.
Flash мёртв? Как плеер в браузерах — безусловно, как инструмент для анимаций — жив и здравствует, альтернатив пока нет и не предвидится. Минусы? Конечно. Главный минус в том, что это не скелетная анимация, хах. Мы лишаемся возможности смешивать анимации друг с другом, у нас нет инверсной кинематики и тому подобных возможностей, присущих только скелетке. Зато у нас есть вложенные друг в друга таймлайны, а также растровые и векторные маски! Все аниматоры обожают маски! Тут стоит оговориться, что в Spine тоже недавно появились возможности отсечения, но пока только геометрические и сильно ограниченные по сравнению с масками flash'а.
Итоги по типам анимаций
Естественно, я перечислил не все виды анимаций, только основные. За бортом остались, например, так называемые процедурные анимации, когда объекты или их фрагменты приводятся в движение только лишь кодом, но противопоставить их другим не представляется возможным, а раздувать статью не очень-то и хотелось, лучше посмотреть что-нибудь более профильное по анимациям для полноты картины.
Подытожим. Серебряной пули, как всегда и почти во всём, нет. Нужно выбирать под проект, задачи и людей. В конце концов, никто никого не ограничивает в том, чтобы использовать один тип анимаций. Повсеместно встречаются проекты где используются все типы анимаций одновременно и все довольны. Кат-сцены — видео, персонажи — скелетка, декорации — таймлайн, полёты эмиттеров систем частиц — процедурная, сами частицы в их системах — покадровая. Берём от каждого типа лучшее.
Муки выбора и решение писать своё
Что ж, по некоторым объективным причинам, мы решили, что нам нужны flash-анимации для нашего проекта. Встал вопрос об интеграции их в движок. Перепробовав несколько вариантов плагинов, мы остановились на одном, который устраивал нас своими возможностями, был жив и поддерживаем, а также имел совершенно нескромную цену для интерпрайз лицензии, но за хороший продукт можно и потерпеть. Я намеренно не буду приводить никаких названий, чтобы не делать никому ни рекламы, ни антирекламы.
С плагином этим мы прожили почти год разработки, в течение которого были выяснены детали, по которым использовать его дальше мы не смогли. А именно — качество его интеграции в Unity. Выливалось это всё в откровенно серьёзные баги, которые не сказать, чтобы легко, быстро и без боя чинились на стороне разработчиков, и в ужасной производительности на целевых устройствах, а у нас это до сих пор уровень iPad 2. Происходило это из-за неграмотной интеграции в конкретный движок, незнания его специфики и подводных камней, по возможностям нас всё более чем устраивало, но исходники, даже в личном порядке, открывать отказались, так и решено было писать своё решение.
Я имел некоторый, вполне успешный, опыт написания расширений для Unity и имел небольшой опыт конвертации flash-анимаций в свои форматы. Последнее было достаточно давно, но что-то вспоминалось. В этом же месте было решено писать это всё как домашний проект, чтобы не зависеть ни от проекта, ни от компании где я работаю, да и что греха таить, иметь такой свой продукт тоже хотелось. Так что, вооружившись 250-и страничной спецификацией swf-формата, я отправился в бой.
Варианты экспорта
Для начала стоит обсудить варианты экспорта анимаций из flash-редактора. Их несколько, естественно со своими плюсами и минусами. Пройдёмся по основным.
.xfl-формат
В редакторе flash-анимаций есть возможность сохранить исходник анимации в несжатый .xfl-формат вместо закрытого .fla-формата, который предлагается по умолчанию. Формат достаточно простой, но незадокументированный. Состоит из нескольких вложенных директорий и кучки .xml-файлов, где и описываются все состояния клипов, хранящихся внутри. Формат описания тоже простой и понятный, вот пример:
static_clip.xml
<DOMSymbolItem name="static_clip" itemID="5c719f28-00000051" lastModified="1550950184">
<timeline>
<DOMTimeline name="static_clip">
<layers>
<DOMLayer name="Layer_1" color="#00FFFF" current="true" isSelected="true">
<frames>
<DOMFrame index="0" keyMode="9728">
<elements>
<DOMBitmapInstance selected="true" libraryItemName="bitmap.png"/>
</elements>
</DOMFrame>
</frames>
</DOMLayer>
</layers>
</DOMTimeline>
</timeline>
</DOMSymbolItem>
Ссылка на исходникТут мы имеем статический клип
static_clip
с одним слоемLayer_1
и одним кадром, в котором находится растровое изображение с именемbitmap.png
.
movie_clip.xml
<DOMSymbolItem name="movie_clip" itemID="5c719f30-00000053" lastModified="1550950713">
<timeline>
<DOMTimeline name="movie_clip">
<layers>
<DOMLayer name="Layer_1" color="#00FFFF" current="true" isSelected="true">
<frames>
<DOMFrame index="0" duration="4" tweenType="motion" motionTweenSnap="true" keyMode="22017">
<elements>
<DOMSymbolInstance libraryItemName="static_clip">
<matrix>
<Matrix tx="-50" ty="-50"/>
</matrix>
<transformationPoint>
<Point x="28.5" y="27.5"/>
</transformationPoint>
</DOMSymbolInstance>
</elements>
</DOMFrame>
<DOMFrame index="4" tweenType="motion" motionTweenSnap="true" keyMode="22017">
<elements>
<DOMSymbolInstance libraryItemName="static_clip" centerPoint3DX="128.5" centerPoint3DY="127.5">
<matrix>
<Matrix tx="100" ty="100"/>
</matrix>
<transformationPoint>
<Point x="28.5" y="27.5"/>
</transformationPoint>
</DOMSymbolInstance>
</elements>
</DOMFrame>
</frames>
</DOMLayer>
</layers>
</DOMTimeline>
</timeline>
</DOMSymbolItem>
Ссылка на исходникЗдесь описывается анимационный клип
movie_clip
, который содержит два ключевых кадра с индексами 0 и 4 соответственно. В кадрах находится наш статический клипstatic_clip
по координатам(-50;-50)
и(100;100)
. Между кадрами имеется motion tween (процедурная анимация, в нашем случае это только перенос, без масштабирования и поворотов), соответственно позицию статического клипа, между ключевыми кадрами, можно получить линейной интерполяцией координат из этих кадров.
Естественно, в реальной анимации всё будет, кхм… несколько сложнее и объёмнее, но с этим всем можно разобраться и без документации. Казалось бы, вот оно счастье. И я знаю несколько проектов и компаний, которые вполне успешно пошли этим путём, но с некоторыми ограничениями и сложностями. Эти сложности и есть ложка дёгтя в бочке .xfl, а именно:
- Твины (tweens, русифицировал как мог) бывают сильно разные, как простые: с линейной интерполяцией, так и более сложные: с пользовательскими функциями и графиками этой интерполяции, морфинг векторной графики на твинах, со своими, только для macromedia и adobe понятными, правилами;
- Векторная графика, как и вся анимация, кроме растровой графики, описана в текстовом виде, пример. Соответственно, необходимо писать свой растеризатор, что, ввиду сложностей и особенностей вектора во flash, не представляется возможным на практике;
- Правила проигрывания анимаций, в том числе вложенных, нужно придумывать самому с нуля, документации на это нет и когда какой кадр следует показывать, и что в каких ситуациях твинить, а что оставлять без интерполяции — предстоит выяснить долгими экспериментами, пытаясь покрыть все возможные случаи в тестовых забегах, что является весьма нетривиальным занятием для общего, а не частного, решения.
Это не все трудности данного подхода, но и их достаточно для того, чтобы понять, что для общего решения оно подходит только с сильными оговорками. Давным-давно я пошёл по этому пути для одного из проектов в котором участвовал. Там были flash кат-сцены, с классическими твинами (без пользовательских правил интерполяции и особых функций этой интерполяции), только растровой графикой и без других продвинутых возможностей, которые предоставляет flash-редактор. Частное решение было написано довольно быстро и эффективно, но приходилось сильно ограничивать аниматоров, чтобы они не использовали ничего мало-мальски сложного. Подход имеет право на жизнь и есть несколько библиотек, разной степени запущенности, основанных на нём, но, как я и говорил, с большим количеством оговорок и ограничений.
.jsfl-скрипты
Ещё один, почти рабочий, вариант получить информацию о наших анимациях. jsfl-скрипты позволяют расширять flash-редактор, взаимодействовать с его окружением, изменять анимацию и, конечно, доставать всю нужную информацию о таймлайнах, слоях, клипах и кадрах в них. А ещё он используется аниматорами для автоматизации различных действий, но это уже история из соседней книжки. В общем-то подход наделён всеми минусами подхода предыдущего, так что останавливаться на нём не буду, скажу лишь, что с помощью него можно выгрузить всю нашу анимацию в виде, например, покадровой анимации, а это не путь настоящего джедая (но, естественно, имеет место быть в некоторых проектах). К скриптам этим мы ещё вернёмся в подходе, который выбрал я: для растеризации векторной графики и оптимизации выгружаемой анимации.
AIR-приложение
А вот этот подход уже совсем рабочий и может использоваться как надо. Я выбрал не его, но для справки приведу и этот вариант. Суть подхода в том, что мы создаём AIR-приложение на самом flash или, для эстетов, на Haxe, например, которое будет доставать всю информацию из уже скомпилированной в swf-формат анимации. Мы покадрово проигрываем анимацию внутри нашего приложения, достаём всю информацию о кадрах и сохраняем в нужный нам формат для своего рантайма. Тут решаются все обозначенные выше проблемы:
- не нужно растрировать векторную графику, за нас это сделает flash-рантайм, остаётся лишь достать эту информацию и сохранить растровый результат векторных фрагментов;
- не нужно придумывать правила проигрывания и разбираться с пользовательской интерполяцией твинов, flash-плеер внутри AIR-приложения совершенно точно знает как это делается и сделает почти всё за нас;
Бонусом получаем возможность использовать кадровые скрипты в анимациях (всяческие play()
, stop()
и прочие gotoAndPlay()
, некоторые аниматоры их очень любят). Минусом получаем невозможность экспортировать зацикленные в самом flash анимации, но это не беда, так как можно зациклить их уже в своём рантайме, а аниматоров попросить подготовить всё для этого.
Тут стоит оговориться, что я уверен — всё будет несколько сложнее, чем звучит в моём описании, так как я лично туда не ходил, поэтому подробности рассказать не могу. Пусть прошедшие этот путь поделятся опытом, с удовольствием почитаем об их радостях и невзгодах!
Свой flash-плеер
Это, наверное, самый честный, очевидный и прямолинейный вариант из всех, но и самый сложный. В конце концов так и поступает настоящий flash-плеер, который стоял у большинства из нас в любимом браузере как дополнение к оному. Популярные представители этого жанра: gameswf и, выросший из предыдущего, scaleform. Оба ныне мертвы. Интересно, что использовались они в основном для реализации GUI в различных, в том числе и AAA, проектах. Но нас интересует внутреннее устройство, а не конкретные применения мертвых библиотек.
Любой уважающий себя flash-плеер имеет на борту как минимум следующие вещи:
- парсер swf-формата;
- растеризатор векторной графики;
- реализацию виртуальной машины для запуска ActionScript кода;
- реализацию стандартной библиотеки для пользовательского кода;
Каждый из этих пунктов кричит о том, что такое если и возможно реализовать, то придётся сменить не одну команду для этой задачи и разменять несколько лет разработки до минимальной версии. Даже реализация какой-то малой части, достаточной для проигрывания чего-либо, займёт очень много времени, а дополнительные сложности, в виде очень многих незадокументированных частей всего этого пайплайна, удвоят этот, и без того огромный, срок.
Да, в сети полно попыток реализаций каждой из этих частей, разной степени запущенности и забагованности. Но даже взять за основу эти наработки не представляется возможным, только баги вылавливать оттуда придётся годами, да ненаписанные юнит-тесты писать, перед тем как понять, что в полной мере реализовать всё не получится. Те, кто пытался дополнять gameswf нужными возможностями, понимает о чём я говорю. Честный flash-плеер ещё и векторную графику должен растрировать не заранее, а в рантайме, что скажется таким ударом в производительность, что не каждый проект сможет пережить. Пользуясь случаем, передаю привет выжившим, использовавшим scaleform на мобильных устройствах. В общем самый мрачный и беспросветный путь, не наш выбор.
Комбинируем подходы
Вот и дошли наконец до моего варианта. Изучив возможные пути, у меня прорисовалась одна интересная комбинация. Относительно простая для реализации, посильная одному человеку в объёмах и в то же время достаточно функциональная, чтобы покрыть потребности большинства 2d таймлайн-анимаций.
Во-первых, будем использовать скомпилированную в swf анимацию, чтобы не придумывать различные варианты проигрывания и почти не угадывать как работает flash-плеер изнутри, так как всех возможных кейсов не покрыть, а писать мы стараемся всё-таки общее решение. Также flash-редактор избавляется от всех твинов, которые использовались в анимации, хитро представляя, в скомпилированном swf, голые позиции фрагментов.
Во-вторых, запрещаем использовать скрипты в анимациях. Да, это очень жесткое и печальное требование, но у меня нет команды программистов для реализации честной виртуальной машины и стандартной библиотеки, которую писать бы пришлось почти вслепую. Тут, конечно, виден минус моего подхода против подхода где использовалось AIR-приложение, так как в последнем кое-какие скрипты по внутреннему проигрыванию анимации, использовать было бы можно. С другой стороны, создавать хорошие анимации это сильно не мешает. Для передачи кастомной информации из анимации в игру можно использовать так называемые frame labels
, которые будут видны из кода, а также навешивать пользовательские события на конкретные кадры в виде функций обратного вызова уже с помощью рантайма.
В-третьих, избавляемся от написания собственного растеризатора векторной графики, за невозможностью написать оный для общего случая, чтобы покрыть все варианты и возможности вектора во flash. Вместо него будем растрировать графику до(!) компиляции, с помощью jsfl-скрипта. Заодно, с помощью этого скрипта, будем оптимизировать нашу векторную графику, сливая статические клипы в одну картинку, уменьшать или увеличивать получаемые растровые изображения для наших нужд: качества и/или производительности. Тут также стоит вспомнить о различных вариантах качества/разрешения арта для устройств с разной плотностью пикселей на экране (например, HD и SD арт).
Заключение лирической части
На этом я заканчиваю первую часть статьи. Во второй части будут технические детали реализации, куски кода, картинки(!), оптимизации, трюки и хитрости. Скоро на ваших экранах, не пропустите! Напоследок приведу пару интересных ссылок с информацией по использованию flash-анимаций от коллег по счастью из ZeptoLab и Playrix.