{"id":3824,"url":"\/distributions\/3824\/click?bit=1&hash=a0d33ab5520cacbcd921c07a49fc8ac5b78623b57936b992ce15c804b99210d4","title":"\u041a\u0430\u043a\u0443\u044e \u0440\u0435\u043a\u043b\u0430\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u0434\u0430\u0442\u044c \u043d\u0430 DTF \u0438 \u043a\u0442\u043e \u0435\u0451 \u0443\u0432\u0438\u0434\u0438\u0442","buttonText":"\u0423\u0437\u043d\u0430\u0442\u044c","imageUuid":"75ec9ef4-cad0-549d-bbed-1482dc44e8ee","isPaidAndBannersEnabled":false}
Gamedev
Yegor Zhumikov

Проблемы покадровой анимации в Unity и как их быстро решить Статьи редакции

Покадровая анимация на спрайтах в Unity из коробки — это весело.

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

Давайте сразу очертим ряд проблем, которые хотелось бы решить:

  • сложный механизм клипов;

  • не менее сложный механизм транзакций;

  • невозможность «подменять» спрайты для скинов или просто универсальных контроллеров.

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

Хранение анимации

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

public class SpritesLine : ScriptableObject { public Sprite[] sprites; }

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

public class SpriteLineCreating : MonoBehaviour { [MenuItem("Assets/Create/Sprites line")] public static void CreateSpritesLine() { var asset = ScriptableObject.CreateInstance<SpritesLine>(); var sprites = new List<Sprite>(); foreach (var selected in Selection.objects) { if (selected is Sprite s) { sprites.Add(s); } } asset.sprites = sprites.ToArray(); ProjectWindowUtil.CreateAsset(asset, "Sprite Line.asset"); } }

Теперь достаточно выделить несколько спрайтов, нажать правую кнопку мыши и выбрать Sprite Line. Всё предельно просто.

Осталось прикрутить это всё к самим объектам.

Аниматор

Создадим компонент, который будет устанавливать спрайты в SpriteRenderer. Я не буду расписывать каждый кусок кода — просто выложу на него ссылку, там достаточно много комментариев. Ещё там лежит кастомный инспектор для удобства. Тут выделю просто важные моменты.

Во-первых, я сделал переменную _revalidate, чтобы не слишком часто дёргать SpriteRenderer. Если этого не делать, скрипт будет постоянно обращаться к полю sprite и постоянно подгуружать связанные с полем объекты. Чтобы это возымело какой-то эффект, на сцене должно быть несколько тысяч спрайтов, но лично мне так спокойнее. Лично у меня на M1 при 10 тысячах спрайтов в редакторе _revalidate стало вопросом 20 FPS.

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

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

Велосипеды — это плохо

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

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

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

0
17 комментариев
Написать комментарий...
Tramp Coder

Используем приблизительно такой же подход в Erra: Exordium. Недавно писали статью в инди группе. 

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

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

Ответить
Развернуть ветку
Vlad Merzlyakov

Спрошу здесь: в чем преимущества аниматора Unity перед, скажем, созданием и добавлением анимаций из blender? 

Ответить
Развернуть ветку
Михаил

Ну дык вопрос не корректный, что-то вроде "чем преимущество приготовления борща, над копанием картошки".
В блендере ты создаешь свои анимации, а в аниматоре юнити ты строишь FSM для управления ими.

Ответить
Развернуть ветку
Yegor Zhumikov
Автор

Animator это стейт машина, а в блендере анимируются сами состояния этой стейт машины, как я понимаю. Соответсвенно, не совсем корректное сравнение

Если сравнивать анимирование самих состояний, то да, в Юнити можно делать свои анимации костные (и не только). Тогда можно будет не только мэшем оперировать, но и переменными внутри компонентов Юнити, дочерними элементами и всем-всем-всем. Другое дело, что аниматор (в смысле человек) должен будет освоить инструментарий Юнити

Ответить
Развернуть ветку
Vismar

Писать свой аниматор - довольно спорное решение. Если все анимации, которые будут в игре, требуют лишь простой смены спрайтов по времени - тогда вопросов никаких. В это случае намного лучше и проще написать маленькое решение, с которым будет намного проще работать, нежели с неудобным стандартным от Unity. Однако как только в ваших анимациях появляются дополнительные объекты, необходимость в прерываниях, переходах по условиям, событиях и прочем, то время затраченное на написание своей системы анимации далеко не факт что окупится в итоге. Потому что это все потребует и своих форматов, и системы работающей с этими форматами, так еще и UI для этого дела. Не "вручную" же эти все вещи создавать. А с UI и общим пайплайном становится все еще хуже, если работать с этим инструментом будет не только автор.

Ответить
Развернуть ветку
Yegor Zhumikov
Автор

Поэтому в конце расписал другие варианты

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

Ответить
Развернуть ветку
Vismar

"Инструментарий" тот предназначен для других вещей, а не покадровой анимации. Скорее эта штука в целом имеет функционал, который можно использовать для покадровой анимации, но далеко не всегда оно вообще нужно, раз уж на то пошло.
А ReAnimator имеет кучу болячек, которые я и так перечислил. Чтобы его довести до ума нужно далеко не пару вечеров. Так что такие себе варианты.

Ответить
Развернуть ветку
Михаил
сложный механизм клипов; не менее сложный механизм транзакций; 

Я с 2д сильно много не работал, но в чем была проблема с обычным накидыванием состояний в машину, с последующим их запуском через animator.play/animator.crossfade?

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

А почему не через какой-нить,
[DefaultExecutionOrder(999)]
public class CustomAnimator: MonoBehaviour {}
чтобы Аниматор обновлялся последним, и явно до LateUpdate?

Ответить
Развернуть ветку
Yegor Zhumikov
Автор

1) Потому что зачем тогда вообще аниматор?
2) А почему просто не LateUpdate, он в общем-то как раз для подобных вещей и удобен. Еще и странных чисел не будет

Ответить
Развернуть ветку
Михаил

И кстати, аниматор который ты приводишь в пример на заглавной картинке, это конечно стандартный TransitionHell (и с ним не то чтобы в покадровой анимации будет трудно, а везде) но в реальной жизни "такого" наверное получиться не может. И выглядело бы оно, примерно вот так. Что уже не так трагично)

Ответить
Развернуть ветку
Михаил

1. Чтобы не тратить время на написание велосипедов с функционалом, который уже существует. Вопрос только в образовывающихся проблемах, которые дают стандартные решения, о чем собственно и было интересно услышать. Ну и хуй с ним собственно.
2. Ну это забавно. Как мы принимаем архитектурное решение чтобы убежать от "проблемы порядка выполнения в Update", и прибегаем к "проблеме порядка выполнения в LateUpdate", которая нас почему-то абсолютно перестает ебать) Ну и хуй с ним собственно Х2.
Так, значит так. Удачи. Я не особый любитель общения "вопросом на вопрос", а ты еще к тому-же похоже настроен защищать свои велосипеды до последнего, игнорируя всех и вся. 

Ответить
Развернуть ветку
Yegor Zhumikov
Автор

1. Это не готовый инструмент для всех случаев жизни, чтобы написать свою GTA VII на мобилки. То, что это велосипед — написано. Я его использую, чтобы не создавать скейты и транзакции на все подряд, потому что при малом количестве этого, как вы сами написали, достаточно. А больше то и не нужно. Главная задача, которую решает все это для меня, — скины и их подмена, что в аниматоре не слишком тривиально делать для покадровый анимаций. Собственно, поэтому аниматор я и не использую, если вопрос в этом

2. Опять же, все зависит от пайплайна. Кто потом решит проблему, что 999 мало и нужно больше или еще что-то. Очень странное замечание, кмк

Ответить
Развернуть ветку
Дмитрий Галяк

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

Ответить
Развернуть ветку
Егор Крид

Так суть канваса в анкорах и лэйаутах. Во многих движках используется система как в Unity UI. Тот же киберпанк дико лагает при большом количестве предметов в инвентаре, т.к. перерисовывает весь UI при изменениях.
А если нужны сложные анимации в менюшках, то нужно писать кастомное решение под конкретную задачу - так например сделали разработчики Ori and the Blind Forest

Ответить
Развернуть ветку
Alexandr Soltys

animator.Play(state) разве не легче? Хотя с ecs норм зайдёт

Ответить
Развернуть ветку
Mikhail Klochkov

Используй animancer, на сайте написано почему он лучше

Ответить
Развернуть ветку
I'm Qugurun

Дополнение для работы с покадровой анимацией от создателя Crawl https://youtu.be/2_3xrqjTyes
https://assetstore.unity.com/packages/tools/sprite-management/powersprite-animator-71177 

Ответить
Развернуть ветку
Читать все 17 комментариев
null