Animation via code - examples?

Hello

I would like to create a simple spinning wheel animation in my custom editor window. I understand UIToolkit does not yet support animations, but can be achieved through code.

I have a custom editor window that instantiates a UXML on enable and add’s it to the root. I have access to all the elements, and I noticed each VisualElement has a transform with rotation property.

What is the recommended way to animation via code in the editor? Subscribe to EditorApplication.update, and drive transform? Should I repaint the window every frame too? VisualElements also seem to be anchored to the top left, so I am not sure what the nicest way is to just spin something around its centre.

Any changes you make to the “transform” property are applied after the element has been positioned and sized by the layouting engine, so changing it won’t affect other elements. Knowing that, you can definitely use the transform for animations.

As for ticking the animation, the recommended approach with UI Toolkit is to use the UI Toolkit Scheduler:

myElement.schedule.Execute(() => anyElement.style.top = anyElement.resolvedStyle.top + 1).Every(100); // ms

Execute() returns an scheduled item object that you can keep around and pause/resume/destroy as you wish to manage your animation this way.

Finally, for rotations, you should be able to just use transform. If you need centered anchors, the recommended approach is to rotate a 1px by 1px “parent” element with justify-content: center and align-items: center style properties that contains your main animated element as a child.

3 Likes

I wanted to try this idea of having an anchor of 1x1 with child element but the result is a thin line rather than the 100x100 red square.

My test code.

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
    <ui:VisualElement style="flex-grow: 1; align-items: center; justify-content: center;">
        <ui:VisualElement name="anchor" style="align-items: center; justify-content: center; width: 1px; height: 1px;">
            <ui:VisualElement name="image" style="background-color: rgb(255, 0, 0); width: 100px; height: 100px;" />
        </ui:VisualElement>
    </ui:VisualElement>
</ui:UXML>

7197424--864001--Annotation 2021-06-01 191737.jpg

Nvm… I had to set flex-shrink: 0; for the child item.

1 Like

A found a problem with rotating an element. It causes other elements to loose hover state. As soon as I remover the animation code the hover works fine again. If I make the animation update (the value in .Every(…)) fast enough I can actually not even get a visible hover state over other elements. If the animation update run slower I can see the hover appear and disappear while moving the mouse over element. So it seems like every “tick” of animation update is causing this.

In the below image you can see the tabs and list hover do not work when “Environment” tab is active. It is the one which has the animated black rings to the right of image.

7199401--864397--01.gif

Here is the full code. I tried it with DoTween and it too causes the problems. I’ve also tried with setting focusable false and picking ignore on all elements involved with the animation.

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using DG.Tweening;

namespace Game.UI
{
    public class RotatingElement : VisualElement
    {
        [UnityEngine.Scripting.Preserve]
        public new class UxmlFactory : UxmlFactory<RotatingElement, UxmlTraits> { }

        [UnityEngine.Scripting.Preserve]
        public new class UxmlTraits : VisualElement.UxmlTraits
        {
            private readonly UxmlFloatAttributeDescription step = new UxmlFloatAttributeDescription { name = "step", defaultValue = 1f };
            private readonly UxmlIntAttributeDescription delay = new UxmlIntAttributeDescription { name = "delay", defaultValue = 100 };

            public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
            {
                base.Init(ve, bag, cc);
                RotatingElement item = ve as RotatingElement;
                item.Step = step.GetValueFromBag(bag, cc);
                item.Delay = delay.GetValueFromBag(bag, cc);
            }
        }

        public float Step { get; private set; }    // how much rotation is applied every step
        public int Delay { get; private set; }  // the rotation is applied every this many milliseconds

        // ------------------------------------------------------------------------------------------------------------

        public RotatingElement()
        {
            Init();
        }

        public RotatingElement(float step, int delay)
        {
            Step = step;
            Delay = delay;
            Init();
        }

        public RotatingElement(float step, int delay, int contentSize, VisualElement contentEle)
        {
            Step = step;
            Delay = delay;

            Init();

            contentEle.focusable = false;
            contentEle.pickingMode = PickingMode.Ignore;
            contentEle.style.width = contentSize;
            contentEle.style.height = contentSize;
            Add(contentEle);
        }

        public void SetContentSize(int size)
        {
            foreach (var c in Children())
            {
                c.style.width = size;
                c.style.height = size;
            }
        }

        private void Init()
        {
            pickingMode = PickingMode.Ignore;
            focusable = false;
           
            style.width = 1;
            style.height = 1;
            style.justifyContent = Justify.Center;
            style.alignItems = Align.Center;

            //DOTween.To(() => worldTransform.rotation.eulerAngles, x => transform.rotation = Quaternion.Euler(x), new Vector3(0f, 0f, 360f), 1f).SetEase(Ease.Linear).SetLoops(-1);

            schedule.Execute(() =>
            {
                var r = worldTransform.rotation.eulerAngles;
                r.z += Step;
                transform.rotation = Quaternion.Euler(r);
            }).Every(Delay); // ms
        }

        // ============================================================================================================
    }
}