Здравствуйте, уважаемые господа. Меня зовут Линар Хилажев, я программирую логику, интерфейсы и игры.
Хочу кое-что интересное рассказать… значит начались эти события после того, как была написана статья Между строк: Создание элементов интерфейса.
Значит, потом, ну наитие такое появилось, что нужно написать определенную статью, желательно про анимацию.
Без лишних слов – встречайте: Анимации в 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), мы это сделаем прямо в конструкторе класса.
Обычно 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/
Добавить комментарий