Tweening in Unity: DOTween vs PrimeTween vs DIY

Illustration of a cube following an eased motion path

Tweening (“in-betweening”) animates a value from A to B over time with an easing curve: a button that pops when hovered, a coin that flies to the score counter, a door that swings open. You can write every one by hand with coroutines and Lerp, but a tween library turns ten lines into one. This guide compares the main Unity tween libraries (DOTween, PrimeTween and LeanTween), shows how to write a tiny tween helper yourself, and covers the bugs that come with tweens: killed objects, stacking tweens and timeScale.

What Tweening Gives You

A tween library handles the boring parts of procedural animation:

  • Easing: dozens of curves (OutBack, InOutSine, OutElastic) with no maths.
  • Sequencing: play A, then B and C together, then call a function.
  • Lifecycle: kill, pause, rewind or complete tweens, and stop them automatically when the target is destroyed.
  • Options: delays, loops, yoyo, unscaled time for pause menus.

Tweens are ideal for UI and “game feel” polish. For character animation, use the Animator. For authored cutscenes, use Timeline.

A Tiny DIY Tween With Async Awaitable

If you only need a few tweens, a small helper with Unity 6’s Awaitable is enough:

using System;
using UnityEngine;

public static class Tween
{
    public static async Awaitable To(Func<float, bool> apply, float duration, Func<float, float> ease = null, bool unscaled = false)
    {
        ease ??= t => t;
        float elapsed = 0f;
        while (elapsed < duration)
        {
            elapsed += unscaled ? Time.unscaledDeltaTime : Time.deltaTime;
            if (!apply(ease(Mathf.Clamp01(elapsed / duration)))) return;   // target gone: stop
            await Awaitable.NextFrameAsync();
        }
        apply(ease(1f));
    }

    public static float OutBack(float t) { const float c = 1.70158f; t -= 1f; return t * t * ((c + 1f) * t + c) + 1f; }
    public static float OutCubic(float t) => 1f - Mathf.Pow(1f - t, 3f);
}

// Usage: pop a button in
Vector3 from = Vector3.zero, to = Vector3.one;
await Tween.To(t => { if (!button) return false; button.transform.localScale = Vector3.LerpUnclamped(from, to, t); return true; },
               0.35f, Tween.OutBack);

The callback returns false when the object has been destroyed, which stops the classic “MissingReferenceException from a tween” problem. That’s about the limit of DIY before a library pays off. See our Awaitable vs coroutines guide for cancellation patterns and our Lerp guide for the maths.

DOTween vs PrimeTween vs LeanTween

DOTween PrimeTween LeanTween
Price Free (Pro is paid) Free Free
Allocations per tween Small (pooled) Zero Small
API style transform.DOMove(...) extensions Tween.Position(transform, ...) LeanTween.move(...)
Sequences Yes, very flexible Yes Basic
async/await Via extensions Built in (await a tween) No
Maintenance Mature, huge community Actively developed Rarely updated
Best for Most projects, lots of tutorials Performance-sensitive and mobile, new projects Legacy projects

Our take: DOTween if you want the most examples and integrations, PrimeTween if you’re starting fresh and care about zero garbage. Both are in our indie Asset Store tools roundup.

DOTween Essentials

After importing, run Tools > Demigiant > DOTween Utility Panel > Setup DOTween once. Then add using DG.Tweening;:

// Move, scale, rotate, fade
transform.DOMove(new Vector3(0, 2, 0), 0.5f).SetEase(Ease.OutCubic);
transform.DOScale(1.2f, 0.15f).SetLoops(2, LoopType.Yoyo);
transform.DORotate(new Vector3(0, 180, 0), 1f, RotateMode.FastBeyond360);
canvasGroup.DOFade(0f, 0.3f).SetUpdate(true);   // true = ignore timeScale

// Sequence: slide in, then punch, then callback
Sequence s = DOTween.Sequence();
s.Append(panel.DOAnchorPosY(0f, 0.4f).SetEase(Ease.OutBack))
 .Join(canvasGroup.DOFade(1f, 0.3f))
 .Append(icon.DOPunchScale(Vector3.one * 0.2f, 0.3f))
 .OnComplete(() => Debug.Log("Shown"));

// Tie tween lifetime to the GameObject
transform.DOMoveX(5f, 2f).SetLink(gameObject);

SetLink(gameObject) kills the tween when the object is destroyed. Use it on everything that can be destroyed mid-tween. To restart a hover effect cleanly, kill the old one first: transform.DOKill(); transform.DOScale(1.1f, 0.1f);.

PrimeTween Essentials

PrimeTween uses static methods, allocates nothing, and every tween can be awaited:

using PrimeTween;

Tween.Position(transform, endValue: new Vector3(0, 2, 0), duration: 0.5f, ease: Ease.OutCubic);
Tween.Scale(transform, endValue: 1.2f, duration: 0.15f, cycles: 2, cycleMode: CycleMode.Yoyo);
Tween.Alpha(canvasGroup, endValue: 0f, duration: 0.3f, useUnscaledTime: true);

Sequence.Create()
    .Chain(Tween.UIAnchoredPositionY(panel, 0f, 0.4f, Ease.OutBack))
    .Group(Tween.Alpha(canvasGroup, 1f, 0.3f))
    .Chain(Tween.PunchScale(icon, Vector3.one * 0.2f, 0.3f));

await Tween.Position(transform, target, 1f);   // continue after it finishes
Tween.StopAll(onTarget: transform);

PrimeTween stops tweens on destroyed targets automatically and has a Shake API that’s great for camera and UI shakes.

Choosing Easing Curves

Ease Feels like Use for
Linear Mechanical Progress bars, rotation loops
OutCubic / OutQuad Fast start, gentle stop Most UI movement
InOutSine Smooth, calm Floating, breathing, camera moves
OutBack Overshoots, then settles Popups, buttons appearing
OutElastic / OutBounce Springy, playful Casual games, rewards, sparingly
InQuad / InCubic Slow start, fast end Things leaving the screen

A good default rule: things entering use an Out ease, and things leaving use an In ease. Keep UI tweens short (0.15–0.4 s). Long UI animations feel sluggish.

🎨 Game feel is a craft

Easing, squash-and-stretch and timing come from animation principles. A few books on our list teach them brilliantly for game developers.

See the game dev reading list →

Juice Recipes

// Button hover (DOTween), kill-safe
public void OnPointerEnter(PointerEventData e) { transform.DOKill(); transform.DOScale(1.08f, 0.12f).SetEase(Ease.OutQuad).SetUpdate(true); }
public void OnPointerExit(PointerEventData e)  { transform.DOKill(); transform.DOScale(1f, 0.12f).SetUpdate(true); }

// Coin flies to the score UI then bumps the counter
coin.DOMove(scoreIcon.position, 0.6f).SetEase(Ease.InBack).SetLink(coin.gameObject)
    .OnComplete(() => { Destroy(coin.gameObject); scoreIcon.DOPunchScale(Vector3.one * 0.3f, 0.25f); });

// Damage flash
spriteRenderer.DOColor(Color.red, 0.05f).SetLoops(2, LoopType.Yoyo).SetLink(gameObject);

// Idle float
transform.DOMoveY(transform.position.y + 0.25f, 1.2f).SetEase(Ease.InOutSine).SetLoops(-1, LoopType.Yoyo).SetLink(gameObject);

Pair tweened health bars with the trail technique from our health bar tutorial.

Troubleshooting

MissingReferenceException after changing scenes

A tween is still running on a destroyed object. Use SetLink(gameObject) in DOTween, or kill tweens in OnDestroy. PrimeTween handles this automatically.

Hover animation grows bigger and bigger

New tweens stack on top of running ones. Kill the previous tween on that target before starting a new one.

Tweens freeze in the pause menu

The game sets Time.timeScale = 0. Use unscaled time: SetUpdate(true) in DOTween or useUnscaledTime: true in PrimeTween.

Tween fights a Rigidbody or Animator

Physics or animation overwrites the transform each frame. Tween Rigidbodies with rb.DOMove (uses MovePosition), or tween a parent object the Animator doesn’t control.

UI tween moves to the wrong place

You tweened transform.position (world space) on a UI element. Use anchored position tweens (DOAnchorPos / Tween.UIAnchoredPosition).

FAQ

Is DOTween free?

Yes. DOTween is free on the Asset Store. DOTween Pro adds a visual editor and extra integrations for a fee.

DOTween or PrimeTween?

DOTween has the largest community and most examples. PrimeTween is newer, allocation-free and supports await natively. Both are excellent choices.

Can I tween without a library?

Yes, with coroutines or async methods and Lerp plus an easing function. It’s fine for a handful of tweens.

How do I tween UI elements?

Tween RectTransform anchored positions and CanvasGroup alpha rather than world positions.

Do tweens hurt performance?

Hundreds of active tweens are fine. PrimeTween and pooled DOTween tweens create little or no garbage.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *