Между строк: Анимации в UI Toolkit

от автора

Здравствуйте, уважаемые господа. Меня зовут Линар Хилажев, я программирую логику, интерфейсы и игры.

Хочу кое-что интересное рассказать… значит начались эти события после того, как была написана статья Между строк: Создание элементов интерфейса.

Значит, потом, ну наитие такое появилось, что нужно написать определенную статью, желательно про анимацию.

Без лишних слов – встречайте: Анимации в Unity UI Toolkit.


Глава 1: Концепция анимации

Давайте начнем с объяснения того, как будет работать анимация для наших элементов интерфейса.

Здесь речь не идет о переходах/задержках и других встроенных возможностях анимации в UI Toolkit.

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

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

Если разложить анимацию на базовые компоненты, то это будет изменение состояния объекта (размер, форма, цвет, положение и т.д.).

Первая задача, перед которой мы стоим, — научиться вызывать внутренние методы класса VisualElement с определенным интервалом времени.

Для этого мы будем использовать внутреннюю реализацию интерфейса IVisualElementScheduler.

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

public RombElement() {     schedule.execute(MethodName); } 

В этом примере кода, при создании элемента мы обращаемся к методу Execute передавая в него Action.

Это значит, что этот метод будет вызван сразу после выполнения конструктора.

Стоит понимать, что выполнен он будет только один раз, а как нам сделать многократные вызовы?

– Я рад, что вы спросили, отвечаю:

public RombElement() {     schedule.Execute(MethodName).Every(16); } 

Мы добавляем к нашему вызову .Every, что означает, наш метод будет вызываться каждые X миллисекунд.

С базой разобрались, давайте усложнять.


Глава 2: Анимации цвета

Под анимацией цвета мы будем обозначать изменение цвета/прозрачности и связанное с его составляющими у UI Element’a

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

Как и всегда, приведу весь код, а потом будет разбирать детально.

using UnityEngine; using UnityEngine.UIElements;  namespace CustomElements {     [UxmlElement]     public partial class RombElement : VisualElement     {         private float _timeLeft;                  private const float AlphaValue = 255;                  [UxmlAttribute] public float AnimationTime = 3f;          public RombElement()         {             generateVisualContent += GenerateVisualContent;              _timeLeft = AnimationTime;             schedule.Execute(ChangeColorAnimation).Every(16);         }          private void ChangeColorAnimation()         {             _timeLeft -= Time.deltaTime;                          firstColor.a = (byte)Mathf.Lerp(firstColor.a, AlphaValue, Time.fixedDeltaTime / _timeLeft);             secondColor.a = (byte)Mathf.Lerp(secondColor.a, AlphaValue, Time.deltaTime / _timeLeft);             thirdColor.a = (byte)Mathf.Lerp(thirdColor.a, AlphaValue, Time.fixedDeltaTime / _timeLeft);             fourColor.a = (byte)Mathf.Lerp(fourColor.a, AlphaValue, Time.deltaTime / _timeLeft);              MarkDirtyRepaint();         }          Vertex[] vertices = new Vertex[4];         ushort[] indices = { 0, 1, 2, 2, 3, 0 };          private Color32 firstColor  = new (255, 0, 0, 0);         private Color32 secondColor  = new (0, 255, 0, 0);         private Color32 thirdColor  = new (0, 0, 255, 0);         private Color32 fourColor  = new (17, 55, 55, 0);          void GenerateVisualContent(MeshGenerationContext mgc)         {             vertices[0].tint = firstColor;             vertices[1].tint = secondColor;             vertices[2].tint = thirdColor;             vertices[3].tint = fourColor;              var top = 0;             var left = 0f;             var middleX = contentRect.width / 2;             var middleY = contentRect.height / 2;             var right = contentRect.width;             var bottom = contentRect.height;              vertices[0].position = new Vector3(left, middleY, Vertex.nearZ);             vertices[1].position = new Vector3(middleX, top, Vertex.nearZ);             vertices[2].position = new Vector3(right, middleY, Vertex.nearZ);             vertices[3].position = new Vector3(middleX, bottom, Vertex.nearZ);              MeshWriteData mwd = mgc.Allocate(vertices.Length, indices.Length);             mwd.SetAllVertices(vertices);             mwd.SetAllIndices(indices);         }     } } 

Обратим наше внимание на конструктор класса, мы там можем заметить вызов метода scheduler’а.

Мы хотим вызывать метод ChangeColorAnimation каждые 16 ms.

Дальше идет само тело метода анимации, в нем мы можем заметить _timeLeft, это переменная для отслеживания времени анимации, которое изначально равно AnimationTime.

Кстати, его можно настроить напрямую через UI Builder, благодаря новому атрибуту [UxmlAttribute].

Он появился в новой версии пакета, подробнее о нововведениях можно прочитать в моей статье.

Мы немного отвлеклись, вернемся к нашим анимациям.

private void ChangeColorAnimation() {     _timeLeft -= Time.deltaTime;          firstColor.a = (byte)Mathf.Lerp(firstColor.a, AlphaValue, Time.fixedDeltaTime / _timeLeft);     secondColor.a = (byte)Mathf.Lerp(secondColor.a, AlphaValue, Time.deltaTime / _timeLeft);     thirdColor.a = (byte)Mathf.Lerp(thirdColor.a, AlphaValue, Time.fixedDeltaTime / _timeLeft);     fourColor.a = (byte)Mathf.Lerp(fourColor.a, AlphaValue, Time.deltaTime / _timeLeft);      MarkDirtyRepaint(); } 

Дальше у нас идет четыре строки, где мы описываем, что оттенки наших вершин должны стремиться от 0 к 255.

После чего, мы вызываем метод MarkDirtyRepaint(), для того чтобы спровоцировать вызов метода GenerateVisualContent(…).


Глава 3: Новые повороты

Давайте признаемся, анимация цвета — это самое простое, что можно придумать.

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

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

В таком случае рассмотрим следующий пример, предположим, мы хотим чтобы у нас была анимация для колокола.

Концептуально, мы не делаем ничего сложного, сперва меняем точку вращения, а потом вращаем VisualElement*. (меняем значение rotate).

Зачем менять точку вращения у элемента? Чтобы логически было проще с этим работать и анимировать.

Для этого нам нужно изменить свойство transform-Origin, которое принимает два значения (X, Y), мы это сделаем прямо в конструкторе класса.

Также можно менять из UI Builder'а

Также можно менять из UI Builder’а

Обычно transform-Origin у элемента это center-center, то есть (X = 50%, Y = 50%), а мы хотим, чтобы он был top-center (X = 0%, Y = 50%).

Более подробно можно посмотреть в документации

Теперь, приступаем к основной части нашего мероприятия, написание самой анимации.

using UnityEngine.UIElements;  namespace CustomElements {     [UxmlElement]     public partial class ImageAnimation : VisualElement     {         [UxmlAttribute] private float AngleLimit = 25;         [UxmlAttribute] private bool AnimationIsStopped = true;                  private bool _rotationDirectionIsMinus = true;                  public ImageAnimation()         {             style.transformOrigin = new TransformOrigin( Length.Percent(50), 0);             schedule.Execute(PlayAnimation).Every(16);         }          private void PlayAnimation()         {             if (AnimationIsStopped)                 return;                          RotateBell();             MarkDirtyRepaint();         }          void RotateBell()         {             var newRotate = style.rotate;             Angle rotateAngle = newRotate.value.angle;              if (_rotationDirectionIsMinus)             {                 rotateAngle.value--;                 if (rotateAngle.value < -AngleLimit)                 {                     _rotationDirectionIsMinus = false;                 }             }             else if (_rotationDirectionIsMinus == false)             {                 rotateAngle.value++;                  if (rotateAngle.value > AngleLimit)                  {                      _rotationDirectionIsMinus = true;                  }             }                          newRotate.value = new Rotate(rotateAngle);             style.rotate = newRotate;         }     } } 

Так выглядит весь класс, наш взор падает на метод RotateBell().

Основной составляющей анимации в данном случае является изменение поворота изображения.

Мы вращаем изображение влево и вправо до тех пор, пока не достигнем предела AngleLimit.

Для этого мы изменяем значение свойства style.rotate и затем вызываем метод MarkDirtyRepaint(), чтобы нам перерисовали VisualElement.

Получаем такой результат.


Глава 4: Новые горизонты?

Сегодня мы разобрались, как можно анимировать элементы интерфейса в UI Toolkit’е и на этом закончим со статьями про кастомные элементы/анимации в Unity.

Если у вас есть вопросы или не поняли какую-то часть, приглашаю вас в комментарии 🙂

Спасибо за внимание и до скорых встреч!


ссылка на оригинал статьи https://habr.com/ru/articles/832798/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *