[Из песочницы] Анимация в Spine, советы и рекомендации, псевдо 3D эффект

Хочу поделиться своим опытом работы со Spine — программой для создания скелетной анимации, специально заточенной под игры.

После просмотра официального гайда возникает много вопросов, поговорим про то, какие подводные камни ожидают нас при работе с этой программой на каждом этапе рабочего процесса (на примере Spine — Unity), как можно оптимизировать свою работу, а так же рассмотрим некоторые популярные фишки типа 3D эффекта. В статье будет много тяжелых гифок.

Сразу оговорюсь, для создания программной анимации в играх есть и другие решения, Dragon Bones, Spriter, Creature, Marionette studio, плагин Puppet 2D и наверняка найдутся другие, просто я как аниматор работаю в Spine.

Наглядная демонстрация принципа скелетной анимации
2d58040f56c0403796870631de64cbe6.gif

Схема работы


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

Общая схема работы такова:
1220b33eea1f44b1af87b6f1dc19a1cb.png
Первым делом необходимо подготовить рабочий материал из графических редакторов. Затем ассеты (assets, текстуры) импортируются в Spine и анимируются. На выходе мы получаем Json — файл в котором записаны все ключевые кадры трансформации костей, слотов и проч. Данный файл импортируется в движок, создается специальная skeleton data, которая добавляется на сцене в Spine game object, где наша анимация визуализируется по средствам mesh renderer, запуском можно управлять с помощью кода или же стандартным unity animator.

Непосредственно рабочий процесс в спайне выглядит следующим образом:

  1. Импорт текстур
  2. Риггинг (настройка скелета)
  3. Скиннинг (настройка меша и привязка его к костям)
  4. Анимация
  5. Экспорт Json и проверка

В качестве примера попробуем сделать анимацию вот такого персонажа:
20f9f42769714c75b7aa8eea0c686a12.jpg

Импорт текстур


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

Типичные ошибки:

не рисовать текстуры движущихся частей где их не видно:
57d70f18a9d14c0eaa7681ebdf346c5a.gif

неправильно поставленные тени
57da19c9bff645b789afb37b0da701b7.gif

А также недостаточное число проекций если персонаж меняет ракурс.

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

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

Процесс импорта текстур можно существенно ускорить используя скрипт layers to PNG, который сохраняет каждый слой из фотошопа в отдельное png изображение, при этом записывает Json файл в котором содержится информация о расположении текстур, импортируя его в спайн мы получаем готового собранного персонажа.

Как пользоваться скриптом layers to PNG
1) Экспортируем из фотошопа с помощью скрипта Json и PNG картинки (можно указать скеил фактор)
2) Импортируем json в спайн file-import data
3) Указываем путь к текстурам
подробнее
0f4503ab0322469f9395fc0f6c8420a2.gif

Однако у такого способа есть ряд недостатков — скрипт сохраняет изображения без сжатия, что непрактично. Просто пересохранив слои через file-generate-image assets мы уменьшим вес изображения приблизительно в 10 раз.

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

ce74b5fd046e42008b9ec3d6815b0299.jpg

Если для проекта необходима покадровая анимация (напр. огонь, спецэффекты, и проч.) — материалы необходимо отрисовать заранее. Далее с помощью adobe after effects и скрипта ae_to_spine мы можем в пару кликов перенести последовательность кадров в Spine.

Как пользоваться скриптом AE to Spine
Аналогично скрипту layers to png. Подробнее
c46a5e7939b345f69bf19640b954e001.gif

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

Тут во многом нам может помочь инструмент find and replace который позволяет быстро переименовывать, указывать путь для большого количества объектов.

инструмент find and replace
5b3a38a8f99d412281a3b7c34a0f1091.gif

Риггинг


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

Тут может помочь введение дополнительных костей-контролов для разбиения движения по осям X и Y (т.к. в спайне остутствует функция separate dimentions).

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

952800c5a4b746908cc770767c26af58.gif


Тут же маленький типс, если вдруг чувствуется нагромождение из за излишнего количества костей — попробуйте покрутить настройку bone scale:

1bf541edaa224285b9593958e43a7c37.jpg

Смотрите примеры готовых ригов из образцов поставляемых вместе со спайном, или в сети.

Небольшая подборка достаточно известных авторов, у которых есть что подсмотреть:
Søren Nielsen
Hwadock Jang
Charles Duchesne
Dao Le Trong
Spine progress
Walk test

Так же полезные материалы можно искать на behanсe, форуме, соответствующих сообществах в социальных сетях.

Скиннинг


Меш — Один из ключевых инструментов в спайне, накладывая на его текстуру мы получаем возможность ее деформировать, искажать, создавать иллюзию 3D.

При создании меша помните: чем меньше вертексов — тем проще контролировать анимацию.

Демонстрация
79edac32b18a48fab70d5be0532087c5.gif

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

2, 3 вариант — хорошо:
3c9463075f9d45c68aea3e9773a47515.gif
f5410289862145fdb7e22a814577c890.gif

4 вариант — излишнее количество точек, которые не выполняют никакой функции, никуда не годится:
c949e1a3f1c547669ae2182411b2068a.gif

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

Производительность


В конечном итоге у нас возникает вопрос —, а сколько вертексов можно использовать?
Разработчики спайна конкретно не отвечают на этот вопрос, и отсылают к вкладке Spine Metrics, где мы можем отследить суммарное количество элементов в сцене, но при этом говорят о необходимости проведения кастомных тестов производительности.

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

И так, когда наш персонаж настроен можно переходить к анимации:

ffd4019c88a742089eb36dce0e10cd5f.jpg

Анимация


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

3D-эффект


Это то за что так любят спайн — возможность деформировать изображение с помощью меша тем самым создавая трехмерный эффект.
Демонстрация
30dc40bbefd84d1497c3cc134a30f755.gif

Для себя я отметил несколько способов создания псевдо 3Д:
  1. Анимация непосредственно вертексов в меше
  2. Много костей в ключевых точках меша, анимируются кости
  3. Кости ставятся выборочно, а далее тщательно настраиваются веса в меше
  4. Комбинированный способ, все сведено под глобальные контролы

Наглядно принцип построения трехмерного эффекта можно рассмотреть на геометрических примитивах:
Куб
Проставляются кости в ключевых точках меша:

8526c2fc8b9447d09f8048e34b2c22b2.gif
Анимируя кости создаем трехмерный эффект:

b3eb12dad5b6456e848d22653d434ee9.gif
90dd4aba57554d30bdb04d17e9a5ab07.gif


Немного сложнее пример со сферой:
362609531cdc4a50a631a50d1ef98140.gif

Можно сделать анимацией вертексов в меше, но как мы видим по сравнению с кубом точек значительно больше:

f56dbd1063cb4d62a94a877c330f5ccc.gif

Аналогично кубу, кости в ключевых точках меша. По затраченному времени этот способ не лучше предыдущего:

19c42390efb241eab740c6c57b8c2553.gif

Было бы гораздо более гибко свести риг под один глобальный контрол с помощью констрейнов, двигая одним управлять всеми костями:

045a52763aa24dafbf9230dcaf99d779.gif

То же самое, можно сделать без лишней массы костей,

d210e78b3b794c758fe7a2bc4d7feff6.gif

Для этого каждому вертексу тщательно подбирается значение в весах. Такой способ можно считать оптимальным для округлых поверхностей.

0f509ee722184314a93f1dc0b15e6264.gif


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

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

И так, мы просидели несколько часов над ключевыми кадрами, мешами, и сделали анимацию персонажа:

Что получилось
550d062c1b34498d945a394a8b6a98ec.gif

Тестирование


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

Если ваш аниматор не дружит с движком, можно использовать Skeleton Viewer

Какие проблемы возникают наиболее часто:

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

Некорректное отображение текстур


Чаще всего такое происходит из за неправильной композиции текстуры — меш должен четко соответствовать размеру спрайта.

4a1bdc4f3cd04ec2b7a45fe977393dbf.jpg

Иначе будет вот так.
f7f2f8613ca3408aad59adc395abf97e.gif

После отработки анимация выглядит не так как раньше


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

a8c863d0523a4a369c4b6a36d77279c6.gif


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

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

Красным выделена иконка фильтра объектов по костям и кнопка для быстрого разворачивания иерархии:

c7d1cf5ded024e92b6311a649d020c69.JPG

Горячие клавиши по умолчанию:

Key Active: L
Key Selected: ctrl + L
Key Dopesheet: ctrl + shift + L
Key Translation, Key Rotation, Key Scale, Key Shear, Key Color — нужно настраивать самому

2) Использовать скрипт setToSetupPose, который как бы и заставляет проигрываться анимацию из дефолтного состояния. Но такой метод имеет существенный недостаток — Setup Pose выставляется мгновенно, а рендер меняет картинку на соответствующую только со следующего кадра. Таким образом мы имеем проскакивание лишнего кадра между анимациями, выглядит это неприятно. Соответствующее issue уже стоит на доске у разработчиков.
Setup Pose баг
1d37c4856fc94261af3792b1c9aaff27.gif

JSON


Мы можем экспортировать из спайна Json, провести с ним какие-либо манипуляции, и импортировать обратно, получив определенный результат. Это очень мощный инструмент т.к. мы получаем доступ ко всем компонентам проекта, в свою очередь нам это может очень помочь в определенных ситуациях:
  1. При необходимости смержить несколько проектов
  2. При необходимости скопировать иерархию костей вместе с анимацией
  3. При необходимости откатиться на более старую версию
  4. Любое другое применение, которое придет вам на ум

Мержинг проектов


Часто возникает необходимость держать анимации в одной сцене, например как тут:
Лягушка ест жука
Сам жук в игре появляется в множестве других сцен и был скопирован из одной из них. Однако теперь в проекте два скелета, и соответственно на выходе будет 2 json и 2 объекта на сцене, что добавит необходимость делать определенную логику синхронного запуска этих анимаций для их совмещения во времени, а так же нужно будет точно совместить объекты по положению в локации. Можно упростить себе жизнь отдав материал в одной сцене. Для этого два json нужно смержить.

2f145218a3084f878bcf62b25cfa9a5c.gif


Для этого есть как минимум два способа: руками и скриптом.Руками
Копируем куски кода из одного json в другой, в соответствующие категории: слоты к слотам, кости к костям, анимации к анимациям, и т.д. Что бы не возникло конфликтов, важно соблюсти корректность наименования объектов, что бы названия из одного json не совпадали с названиями из другого. Проще всего этого добиться заранее в спайне присвоив всем объектам в именах свой уникальный индекс типа _skel1_ и _skel2_. Этот способ слегка затратный по времени, но меня еще не подводил.
Примеры
Предельно простой пример для демонстрации принципа, два скелета с костями в одном проекте:

d4ae9c3c2f9e43719f40c615d1eae749.JPG

Скелет 1
{
"skeleton": { "hash": "ZMTMZiuTD2M2gnBhJR0JLPQWOws", "spine": "3.4.02", "width": 0, "height": 0, "images": "" },
"bones": [
	{ "name": "root_skel1_" },
	{ "name": "bone1_skel1_", "parent": "root_skel1_", "length": 26.95, "rotation": 360, "x": 11.09, "y": -9.65, "color": "00ff00ff" },
	{ "name": "bone2_skel1_", "parent": "bone1_skel1_", "length": 26.95, "x": 26.27, "color": "00ff00ff" },
	{ "name": "bone3_skel1_", "parent": "bone2_skel1_", "length": 26.95, "x": 26.57, "color": "00ff00ff" },
	{ "name": "bone4_skel1_", "parent": "bone3_skel1_", "length": 26.95, "x": 25.97, "color": "00ff00ff" }
],
"animations": {
	"animation": {}
}
}


Скелет 2
{
"skeleton": { "hash": "osF6oBu7PH6sMNfjN7pm2EwQ8fY", "spine": "3.4.02", "width": 0, "height": 0, "images": "" },
"bones": [
	{ "name": "root_skel2_" },
	{ "name": "bone1_skel2_", "parent": "root_skel2_", "x": 19.25, "y": 26.63, "color": "fff100ff" },
	{ "name": "bone2_skel2_", "parent": "bone1_skel2_", "x": 27.14, "color": "fff100ff" },
	{ "name": "bone3_skel2_", "parent": "bone2_skel2_", "x": 25.57, "color": "fff100ff" },
	{ "name": "bone4_skel2_", "parent": "bone3_skel2_", "x": 27.14, "color": "fff100ff" }
],
"animations": {
	"animation": {}
}
}


Соединенные скелеты
{
"skeleton": { "hash": "ZMTMZiuTD2M2gnBhJR0JLPQWOws", "spine": "3.4.02", "width": 0, "height": 0, "images": "" },
"bones": [
	{ "name": "root" },
	{ "name": "root_skel1_", "parent": "root" },
	{ "name": "bone1_skel1_", "parent": "root_skel1_", "length": 26.95, "rotation": 360, "x": 11.09, "y": -9.65, "color": "00ff00ff" },
	{ "name": "bone2_skel1_", "parent": "bone1_skel1_", "length": 26.95, "x": 26.27, "color": "00ff00ff" },
	{ "name": "bone3_skel1_", "parent": "bone2_skel1_", "length": 26.95, "x": 26.57, "color": "00ff00ff" },
	{ "name": "bone4_skel1_", "parent": "bone3_skel1_", "length": 26.95, "x": 25.97, "color": "00ff00ff" },
	{ "name": "root_skel2_", "parent": "root"  },
	{ "name": "bone1_skel2_", "parent": "root_skel2_", "x": 19.25, "y": 26.63, "color": "fff100ff" },
	{ "name": "bone2_skel2_", "parent": "bone1_skel2_", "x": 27.14, "color": "fff100ff" },
	{ "name": "bone3_skel2_", "parent": "bone2_skel2_", "x": 25.57, "color": "fff100ff" },
	{ "name": "bone4_skel2_", "parent": "bone3_skel2_", "x": 27.14, "color": "fff100ff" }
],
"animations": {
	"animation": {}
}
}


В результате имеем все в одном проекте:

bc9fd5780bd6414985cda182b28b3a6d.JPG

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

Более сложный пример с лягушкой:
(Осторожно, длинные файлы)

Жук
Лягушка
Жук+Лягушка


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

Дублирование объектов вместе с анимацией


В спайне отсутствует вложенность композиций (аналог прекомпоз в After Effects или символ во Flash), однако, как показывалось выше, есть возможность продублировать объекты, с сохранением всех зависимостей, но ключи анимации в таком случае не копируются. Не сложно догадаться что, для того что бы не делать анимацию заново, можно переназначить ее на дубликат костей, для этого надо всего лишь перебить в Json имена. Опять же, это проще сделать если у каждой кости будет свой уникальный индекс.
Пример
Есть муравей с анимацией. Задача сделать в спайне целую цепочку бегающих друг за другом муравьев. При дублировании муравья анимация не копируется. Обратите внимание, у первого муравья в названии стоит индекс _skel_1_, у второго _skel_2_.

ea30cb79c29a492d9cf2d77143344130.gif

Экспортируем Json и открываем его текстовым редактором. Код анимации walk_1 копируем, находим там все участки _skel_1_:

"animations": {
	"walk_1": {
		"bones": {
			"US_2_skel_1_": {
				"rotate": [
					{
						"time": 0,
						"angle": -42.21,
						"curve": [ 0.25, 0, 0.75, 1 ]
					},
					{
						"time": 0.4,
						"angle": -0.92,
						"curve": [ 0.25, 0, 0.75, 1 ]
					},
					{ "time": 0.8, "angle": -42.21 }
				]
			},
....

заменяем их на _skel_2_
....
			"US_2_skel_2_": {
				"rotate": [
					{
						"time": 0,
						"angle": -42.21,
						"curve": [ 0.25, 0, 0.75, 1 ]
					},
					{
						"time": 0.4,
						"angle": -0.92,
						"curve": [ 0.25, 0, 0.75, 1 ]
					},
					{ "time": 0.8, "angle": -42.21 }
			},
...

Вставляем обратно. Таким образом мы переназначили анимацию на другие кости. Сохраняем json, и импортируем его в спайн путем file — import data. Теперь у нас два бегающих муравья.

c555566f65034f60b22ff10eb4b3f6fa.gif


Откатиться до старой версии


Проекты сохраненные в более новой версии будут не совместимы со старыми. Иногда это может стать проблемой, и для ее решения есть несколько способов: Руками перебить версию в json
"skeleton": { "hash": "", "spine": "3.4.02", "width": 0, "height": 0, "images": "" },

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

Резюме


  • Четко обозначайте какие задачи в чем вы делаете
  • Используйте скрипты для ускорения работы
  • Делайте гибкий риг и не перебарщивайте с мешем
  • Перед отправкой тестируйте материал в рантайме
  • Не стесняйтесь залезть в json если это вам сэкономит время

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

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

Комментарии (0)

© Habrahabr.ru