Creating A Custom Transition Animator Using DOTween




Canvas Flow allows you to fully customize the animations between canvas controllers, as outlined in Custom Transition Animators. The reference documentation, as well as the included built-in animators, all use the Routine class to animate values over time, such as the position of the canvas controller's content. Whilst you are free to also use this class and follow this pattern, some people may feel more comfortable using the popular DOTween animation framework. Therefore, this tutorial will show you how you can easily use DOTween in a custom transition animator to do your animation. Below are the two example transition animations we will create.

Slide Spin



Firstly, if you haven't done so already, make sure DOTween is imported into your project in the usual way.

Create a new custom transition animator

Begin by creating a new custom transition animator. This is done by selecting Create/Canvas Flow/Custom Transition Animator in Unity's Create menu, found in the menu bar at Assets/Create or by right-clicking in the project window.

This will create a new script at the specified location, with two methods for us to implement.

Implement the AnimationTransition method

As documented in the manual, we perform our animation in the AnimateTransition method. This method is passed a transition context, which provides us with the canvas controllers involved in the transition, as well as some other transitional information.

public override void AnimateTransition(CanvasControllerTransitionContext transitionContext)
{
    // Perform our animation.
}

Determine the target canvas controller

So firstly, let's get a reference to the canvas controller we want to animate. We get this from the transition context's source and destination properties - but which one? As shown in the videos above, we are going to animate just the presented canvas controller (the yellow one). This canvas controller will be the destination of a presentation transition (downstream), but the source of a dismissal transition (upstream), as shown below:

Action Direction Visual Description (Presented Highlighted)
Present Downstream source ---> destination
Dismiss Upstream destination <---- source

So, we can get our target canvas controller based on the direction of the transition, like so:

CanvasController targetCanvasController = (transitionContext.isUpstream) ?
    transitionContext.sourceCanvasController :
        transitionContext.destinationCanvasController;

Determine the animation's start & end positions

Now let's animate the target canvas controller. In this first example, we will simply slide the canvas controller's content up from the bottom of the screen. We will therefore need to calculate the start and end positions for the content. Again this will vary based upon the direction of the transition - if it is being presented, we want the content to start off-screen-bottom and move on-screen; if it is being dismissed we want to start on-screen and move off-screen-bottom. We can do this like so:

Vector3 contentStartPosition = (transitionContext.isUpstream) ?
    targetCanvasController.OnScreenContentPosition() :
        targetCanvasController.OffScreenBottomContentPosition();

Vector3 contentEndPosition = (transitionContext.isUpstream) ?
    targetCanvasController.OffScreenBottomContentPosition() :
        targetCanvasController.OnScreenContentPosition();

DOTween

And now to actually perform the animation with DOTween. We first set our content's position to the start position and then use DOTween's To method to animate the content over time. Notice how we specify a completion handler on the last line, which calls CompleteTransition. As specified in the documentation, you must call CompleteTransition on the provided transition context once your animation has finished. This is easily done using a DOTween completion handler.

// Set the content position to the start position.
targetCanvasController.ContentPosition = contentStartPosition;

// Tween using DOTween.
DOTween.To(() =>
            targetCanvasController.ContentPosition,
            x => targetCanvasController.ContentPosition = x,
            contentEndPosition,
            duration).OnComplete(transitionContext.CompleteTransition);

The duration property above is specified as a public field on the script. This will allow it to be adjusted in the inspector later, without editing your transition animator's code.

Implement AnimateTransitionForInitialCanvasController to support initial transitions

In an initial transition, the presenter - the canvas controller presenting or being dismissed to - may be null. As we only ever operate on the presented canvas controller, we can just forward to our AnimateTransition implementation, like so:

public override void AnimateTransitionForInitialCanvasController(
    CanvasControllerTransitionContext transitionContext)
{
    AnimateTransition(transitionContext);
}

And that's it! We can use our new animator by creating a new instance of it (Create/Canvas Flow/Your-Transition-Animator-Instance), as described in Using A Transition Animator.

Which gives us a custom transition animated with DOTween:


Multiple DOTweens

Let's create something a little more custom-looking and create a spin and scale up animation. Using the same code as above to determine our target canvas controller, we can now create two DOTweens - one to rotate the content, and one to scale it up.

// Determine the content's start and end scale.
Vector3 contentStartScale = (transitionContext.isUpstream) ?
    Vector3.one : Vector3.zero;
Vector3 contentEndScale = (transitionContext.isUpstream) ?
    Vector3.zero : Vector3.one;

// Tween the scale.
targetCanvasController.ContentScale = contentStartScale;
DOTween.To(() =>
            targetCanvasController.ContentScale,
            x => targetCanvasController.ContentScale = x,
            contentEndScale,
            duration).OnComplete(transitionContext.CompleteTransition);

// Determine the content's start and end rotation.
float rotation = 180f;
Vector3 contentStartRotation = (transitionContext.isUpstream) ?
    new Vector3(0f, 0f, 0f) : new Vector3(0f, 0f, rotation);
Vector3 contentEndRotation = (transitionContext.isUpstream) ?
    new Vector3(0f, 0f, rotation) : new Vector3(0f, 0f, 0f);

// Tween the rotation
targetCanvasController.content.localRotation = Quaternion.Euler(contentStartRotation);
DOTween.To(() =>
            targetCanvasController.content.localRotation,
            x => targetCanvasController.content.localRotation = x,
            contentEndRotation,
            duration);

Notice how we only specify the completion handler once on the scale tween - we only need to call CompleteTransition once when the animation has finished.

Which gives us the following:


Slide Transition Animator Source

For reference, the complete slide transition animator we just created looks like this:

using DG.Tweening;
using P7.CanvasFlow;
using UnityEngine;

// Easily create instances of this transition animator in the Unity menu.
[CreateAssetMenu(fileName = "DOTweenExampleTransitionAnimator",
                 menuName = "Canvas Flow/DOTweenExampleTransitionAnimator Instance",
                 order = 500)]

public class DOTweenExampleTransitionAnimator : CanvasControllerTransitioningAnimator,
    ICanvasControllerTransitioningAnimator
{
    public float duration = 0.4f;

    public override void AnimateTransition(CanvasControllerTransitionContext transitionContext)
    {
        // Determine the target canvas controller - the one being presented or dismissed.
        CanvasController targetCanvasController = (transitionContext.isUpstream) ?
            transitionContext.sourceCanvasController :
                transitionContext.destinationCanvasController;

        // Determine the content's start and end positions.
        Vector3 contentStartPosition = (transitionContext.isUpstream) ?
            targetCanvasController.OnScreenContentPosition() :
                targetCanvasController.OffScreenBottomContentPosition();

        Vector3 contentEndPosition = (transitionContext.isUpstream) ?
            targetCanvasController.OffScreenBottomContentPosition() :
                targetCanvasController.OnScreenContentPosition();

        // Set the content position to the start position.
        targetCanvasController.ContentPosition = contentStartPosition;

        // Tween using DOTween.
        DOTween.To(() =>
                    targetCanvasController.ContentPosition,
                    x => targetCanvasController.ContentPosition = x,
                    contentEndPosition,
                    duration).OnComplete(transitionContext.CompleteTransition);
    }

    public override void AnimateTransitionForInitialCanvasController(
        CanvasControllerTransitionContext transitionContext)
    {
        AnimateTransition(transitionContext);
    }
}

Spin Transition Animator Source

For reference, the complete spin transition animator we just created looks like this:

using DG.Tweening;
using P7.CanvasFlow;
using UnityEngine;

// Easily create instances of this transition animator in the Unity menu.
[CreateAssetMenu(fileName = "DOTweenExampleTransitionAnimator",
                 menuName = "Canvas Flow/DOTweenExampleTransitionAnimator Instance",
                 order = 500)]

public class DOTweenExampleTransitionAnimator : CanvasControllerTransitioningAnimator,
    ICanvasControllerTransitioningAnimator
{
    public float duration = 0.4f;

    public override void AnimateTransition(CanvasControllerTransitionContext transitionContext)
    {
        // Determine the target canvas controller - the one being presented or dismissed.
        CanvasController targetCanvasController = (transitionContext.isUpstream) ?
            transitionContext.sourceCanvasController :
                transitionContext.destinationCanvasController;

        // Determine the content's start and end scale.
        Vector3 contentStartScale = (transitionContext.isUpstream) ?
            Vector3.one : Vector3.zero;
        Vector3 contentEndScale = (transitionContext.isUpstream) ?
            Vector3.zero : Vector3.one;

        // Tween the scale.
        targetCanvasController.ContentScale = contentStartScale;
        DOTween.To(() =>
                   targetCanvasController.ContentScale,
                   x => targetCanvasController.ContentScale = x,
                   contentEndScale,
                   duration).OnComplete(transitionContext.CompleteTransition);

        // Determine the content's start and end rotation.
        float rotation = 180f;
        Vector3 contentStartRotation = (transitionContext.isUpstream) ?
            new Vector3(0f, 0f, 0f) : new Vector3(0f, 0f, rotation);
        Vector3 contentEndRotation = (transitionContext.isUpstream) ?
            new Vector3(0f, 0f, rotation) : new Vector3(0f, 0f, 0f);

        // Tween the rotation
        targetCanvasController.content.localRotation = Quaternion.Euler(contentStartRotation);
        DOTween.To(() =>
                   targetCanvasController.content.localRotation,
                   x => targetCanvasController.content.localRotation = x,
                   contentEndRotation,
                   duration);
    }

    public override void AnimateTransitionForInitialCanvasController(
        CanvasControllerTransitionContext transitionContext)
    {
        AnimateTransition(transitionContext);
    }
}

Disclosure: This post may contain affiliate links, which means I may receive a commission if you click a link and purchase something that I have recommended. While clicking these links won't cost you any money, they will help me fund my development projects while recommending great assets!