DEV Community

GameDevToolLab
GameDevToolLab

Posted on

Is LitMotion the Best Tween Library for Unity in 2026? A Practical Guide

DOTween has been the default answer for Unity tweening for years, and it is still a very strong library. But for a new code-driven project, I would no longer choose it automatically.

After using LitMotion recently, I found its API especially comfortable: Transform values, UI values, and custom values all follow the same Create -> With -> Bind pattern. LitMotion v2 also adds Sequence support, Inspector editing through LitMotion.Animation, debugging tools, and improved cancellation/control APIs.

This article compares LitMotion, PrimeTween, and DOTween, then shows the parts of LitMotion I consider important in production: setup, MotionHandle, object lifetime, Time.timeScale, Sequence, UniTask, Punch/Shake, TextMeshPro, and migration design.

The version information here was checked on July 28, 2026. Recheck release pages if you read this much later.

My recommendation

Situation Recommendation
New project, animation logic mainly in C# LitMotion
Very sensitive to tween CPU/GC cost Benchmark LitMotion and PrimeTween in your real workload
Prefer Tween.Position()-style target APIs PrimeTween
Want short Inspector-edited animations Evaluate LitMotion.Animation, PrimeTween PRO, and DOTween Pro
Existing project already uses DOTween heavily Usually keep DOTween
Third-party assets depend on DOTween Keep DOTween, or strictly separate ownership

For a new code-first project, LitMotion is currently my first candidate.

It emphasizes low-allocation tween creation, uses the C# Job System and Burst, supports arbitrary values, and keeps a consistent API. It is MIT licensed and supports Unity 2021.3 or later.

That does not mean your whole animation path becomes allocation-free. Capturing lambdas, strings, LINQ, async code, Canvas rebuilds, and whatever you do in the binding still matter.

At the research date, LitMotion's latest release was v2.0.2. PrimeTween was also actively maintained, so I consider it a serious alternative rather than a secondary option.

LitMotion, PrimeTween, and DOTween

LitMotion

LitMotion is a data-oriented tween library built around LMotion.

LMotion.Create(0f, 1f, 0.3f)
    .WithEase(Ease.OutCubic)
    .Bind(x => alpha = x);
Enter fullscreen mode Exit fullscreen mode

Create a value transition, add options, then bind it to a target. The same structure works for Transform, UI, TextMeshPro, and your own values.

LitMotion v2 adds LSequence, LitMotion.Animation, TryCancel() / TryComplete() style handle control, and LitMotion Debugger.

PrimeTween

PrimeTween is also designed around modern, low-allocation usage, but its API feels more target-oriented.

Tween.PositionY(
    transform,
    endValue: 10f,
    duration: 1f,
    ease: Ease.InOutSine);
Enter fullscreen mode Exit fullscreen mode

Starting from Tween. is very discoverable in an IDE. If you prefer choosing a target operation first, PrimeTween may feel more natural than LitMotion.

DOTween

DOTween remains excellent when you already have production code and tools around it.

transform.DOMoveX(10f, 0.5f)
    .SetEase(DG.Tweening.Ease.OutCubic);
Enter fullscreen mode Exit fullscreen mode

It has a mature ecosystem, many shortcuts, strong Sequence support, and DOTween Pro. If your project already depends on those strengths and profiling shows no problem, migration can easily cost more than it saves.

What about MagicTween?

MagicTween's repository was archived in 2025 and points users toward LitMotion. I would not choose MagicTween for a new project.

Do not choose from a benchmark alone

The author's public TweenPerformance project shows strong LitMotion results in its tested environment, including very low allocation during tween creation.

But benchmark versions differ from the current libraries, the environment is specific, and real games also pay for bindings, Canvas work, closures, strings, async state machines, materials, and game-specific logic.

Use benchmarks as a hint. Make the final decision from a Player build on your target hardware.

Installing LitMotion

For LitMotion v2.0.2, the package requirements are:

  • Unity 2021.3+
  • Burst 1.6.0+
  • Collections 1.5.1+
  • Mathematics 1.0.1+

Add the package through Unity Package Manager. In a team project, pin a tag or commit.

https://github.com/annulusgames/LitMotion.git?path=src/LitMotion/Assets/LitMotion#v2.0.2
Enter fullscreen mode Exit fullscreen mode

The ?path=... part comes before #v2.0.2.

Typical namespaces are:

using LitMotion;
using LitMotion.Extensions;
Enter fullscreen mode Exit fullscreen mode

UniTask integration requires com.cysharp.unitask. TextMeshPro integration requires TMP. If you use custom asmdefs, verify references to LitMotion, LitMotion.Extensions, UniTask, and Unity.TextMeshPro as needed.

The snippets in this article are based on LitMotion v2.0.2's public API and source. I would still compile representative examples in a minimal Unity project before adopting them as team standards.

The core pattern: Create, configure, Bind

LMotion.Create() returns a builder. The motion becomes useful when you configure it and finally bind it.

LMotion.Create(0f, 1f, 0.3f)
    .WithEase(Ease.OutCubic)
    .Bind(x => alpha = x);
Enter fullscreen mode Exit fullscreen mode

For a Transform:

LMotion.Create(target.position, destination, 0.4f)
    .WithEase(Ease.OutCubic)
    .BindToPosition(target)
    .AddTo(target.gameObject);
Enter fullscreen mode Exit fullscreen mode

For an arbitrary value:

LMotion.Create(volume, 1f, 0.5f)
    .WithEase(Ease.Linear)
    .Bind(x => volume = x)
    .AddTo(gameObject);
Enter fullscreen mode Exit fullscreen mode

A capturing lambda can allocate. If this is a hot path, use a state-passing overload instead:

LMotion.Create(volume, 1f, 0.5f)
    .Bind(this, (x, self) => self.volume = x)
    .AddTo(gameObject);
Enter fullscreen mode Exit fullscreen mode

For built-in properties, dedicated BindTo...() helpers are usually clearer.

Ease, delay, and loops

Builder options compose naturally:

LMotion.Create(0f, 1f, 0.25f)
    .WithDelay(0.1f)
    .WithEase(Ease.OutBack)
    .Bind(x => value = x);
Enter fullscreen mode Exit fullscreen mode

List entrance effects are easy to stagger:

for (var i = 0; i < items.Length; i++)
{
    var item = items[i];

    LMotion.Create(0f, 1f, 0.25f)
        .WithDelay(i * 0.04f)
        .WithEase(Ease.OutCubic)
        .BindToLocalScaleXYZ(item)
        .AddTo(item.gameObject);
}
Enter fullscreen mode Exit fullscreen mode

For infinite loops:

LMotion.Create(0.95f, 1.05f, 0.5f)
    .WithLoops(-1, LoopType.Yoyo)
    .BindToLocalScaleXYZ(transform)
    .AddTo(gameObject);
Enter fullscreen mode Exit fullscreen mode

-1 means the motion does not end by itself, so always provide a lifetime through object destruction, a stored handle, or cancellation.

MotionHandle: stop old animations before starting new ones

Bindings return a MotionHandle.

public void Replay()
{
    handle.TryCancel();

    handle = LMotion.Create(0f, 1f, 0.4f)
        .WithEase(Ease.OutCubic)
        .Bind(x => value = x);
}
Enter fullscreen mode Exit fullscreen mode

Use TryComplete() when you want to apply the final value instead of stopping at the current one.

The bigger design issue is property ownership. If open/close, hover, selection, and click feedback all write the same Scale or Alpha, they can fight each other even if every individual tween is valid.

private MotionHandle fadeHandle;

public void SetVisible(bool visible)
{
    fadeHandle.TryCancel();

    fadeHandle = LMotion.Create(
            canvasGroup.alpha,
            visible ? 1f : 0f,
            0.2f)
        .WithEase(Ease.OutCubic)
        .BindToAlpha(canvasGroup)
        .AddTo(gameObject);
}
Enter fullscreen mode Exit fullscreen mode

Starting from the current value also avoids a jump when the animation reverses halfway through.

AddTo(gameObject) and CancellationToken are different tools

For UI and temporary objects, I normally tie a motion to its owner:

LMotion.Create(0f, 1f, 1f)
    .Bind(x => value = x)
    .AddTo(gameObject);
Enter fullscreen mode Exit fullscreen mode

AddTo(gameObject) cancels the motion when the GameObject is destroyed. It is lifetime ownership, not async-flow control.

LitMotion v2.0.2's ToUniTask(token) uses CancelBehavior.Cancel with cancelAwaitOnMotionCanceled: true. If you want the intent visible in code, write it explicitly:

await handle.ToUniTask(
    CancelBehavior.Cancel,
    cancelAwaitOnMotionCanceled: true,
    cancellationToken: token);
Enter fullscreen mode Exit fullscreen mode

If token cancellation should move the motion to its end value, use CancelBehavior.Complete.

Decide whether a screen token, GameObject lifetime, explicit handle, or caller token owns the animation. Do not add all of them blindly and assume they mean the same thing.

UI animation while Time.timeScale == 0

Pause menus often still need animation after gameplay time is stopped.

LMotion.Create(0f, 1f, 0.2f)
    .WithScheduler(MotionScheduler.UpdateIgnoreTimeScale)
    .WithEase(Ease.OutCubic)
    .BindToAlpha(canvasGroup)
    .AddTo(gameObject);
Enter fullscreen mode Exit fullscreen mode

Useful schedulers include normal Update, UpdateIgnoreTimeScale, FixedUpdate, and the PreLateUpdate / PostLateUpdate families.

The scheduler also changes ordering relative to other scripts, so verify camera, layout, and physics interactions instead of treating this as only a time-scale switch.

Sequence for fixed timelines

LitMotion v2 provides LSequence:

var sequenceHandle = LSequence.Create()
    .Append(
        LMotion.Create(0f, 1f, 0.25f)
            .WithEase(Ease.OutCubic)
            .BindToAlpha(canvasGroup))
    .Join(
        LMotion.Create(0.9f, 1f, 0.25f)
            .WithEase(Ease.OutBack)
            .BindToLocalScaleXYZ(panel))
    .AppendInterval(0.15f)
    .Append(
        LMotion.Create(
                panel.localPosition.y,
                panel.localPosition.y + 20f,
                0.2f)
            .WithLoops(2, LoopType.Yoyo)
            .BindToLocalPositionY(panel))
    .Run()
    .AddTo(gameObject);
Enter fullscreen mode Exit fullscreen mode

Append() is sequential, Join() is parallel, Insert() places a motion at a specific time, and AppendInterval() adds a wait. Run() starts the sequence.

If initial values are being applied too early while assembling a sequence, consider WithImmediateBind(false).

Sequence is best for a fixed timeline. Input waits, network waits, branching, and complex cancellation are usually clearer in async code or a state machine.

UniTask: await handle and ToUniTask() do not cancel the same way

LitMotion supports directly awaiting a MotionHandle, but in v2.0.2 await handle; resumes on either completion or cancellation, and MotionAwaiter.GetResult() does not throw.

If you need CancellationToken integration or want motion cancellation to propagate as async cancellation, use ToUniTask().

A UI animation that can be triggered repeatedly can share one linked CTS so the next play request cancels the previous one:

private CancellationTokenSource playCts;

public async UniTask PlayAsync(CancellationToken cancellationToken)
{
    playCts?.Cancel();

    var currentCts = CancellationTokenSource
        .CreateLinkedTokenSource(cancellationToken);
    playCts = currentCts;
    var token = currentCts.Token;

    try
    {
        var fade = LMotion.Create(canvasGroup.alpha, 1f, 0.25f)
            .BindToAlpha(canvasGroup)
            .AddTo(gameObject);

        var scale = LMotion.Create(panel.localScale.x, 1f, 0.3f)
            .WithEase(Ease.OutBack)
            .BindToLocalScaleXYZ(panel)
            .AddTo(gameObject);

        await UniTask.WhenAll(
            ToCancelableUniTask(fade, token),
            ToCancelableUniTask(scale, token));
    }
    finally
    {
        if (ReferenceEquals(playCts, currentCts))
            playCts = null;

        currentCts.Dispose();
    }
}

private static UniTask ToCancelableUniTask(
    MotionHandle handle,
    CancellationToken token)
{
    return handle.ToUniTask(
        CancelBehavior.Cancel,
        cancelAwaitOnMotionCanceled: true,
        cancellationToken: token);
}
Enter fullscreen mode Exit fullscreen mode

When a new call cancels the previous PlayAsync(), the previous caller observes cancellation. If that is normal UI control flow, handle it at a clear boundary:

try
{
    await resultPanelAnimation.PlayAsync(token);
}
catch (System.OperationCanceledException)
{
    // Re-entry, screen transition, or destruction is normal here.
}
Enter fullscreen mode Exit fullscreen mode

The important point is not to hide cancellation semantics. Decide where cancellation becomes a normal result.

Punch and Shake

Punch is useful for button feedback:

var baseScale = buttonTransform.localScale;

LMotion.Punch.Create(baseScale, Vector3.one * 0.15f, 0.3f)
    .WithFrequency(8)
    .WithDampingRatio(1f)
    .BindToLocalScale(buttonTransform)
    .AddTo(buttonTransform.gameObject);
Enter fullscreen mode Exit fullscreen mode

The second argument is strength, not an end value. Do not pass Vector3.zero as the start value unless you intentionally want to oscillate around zero scale.

Shake works similarly:

var basePosition = cameraRoot.localPosition;

LMotion.Shake.Create(basePosition, new Vector3(8f, 8f, 0f), 0.35f)
    .WithFrequency(20)
    .WithDampingRatio(1f)
    .WithRandomSeed(123)
    .BindToLocalPosition(cameraRoot)
    .AddTo(cameraRoot.gameObject);
Enter fullscreen mode Exit fullscreen mode

For cameras, a dedicated shake-offset transform is often safer than letting Cinemachine, follow code, recoil, and shake all write the same transform.

TextMeshPro integration

LitMotion can animate fixed-size strings:

LMotion.String.Create128Bytes(
        "",
        "<color=#FFD54F>MISSION COMPLETE</color>",
        1.2f)
    .WithRichText()
    .WithScrambleChars(ScrambleMode.Uppercase)
    .BindToText(messageText)
    .AddTo(messageText.gameObject);
Enter fullscreen mode Exit fullscreen mode

128 is a byte capacity, not a character count. Localized Japanese text, emoji, and RichText tags can consume more space than a short English sample. Choose capacity from the longest production string.

Numeric binding is also convenient:

LMotion.Create(0, 9999, 0.8f)
    .WithEase(Ease.OutCubic)
    .BindToText(scoreText)
    .AddTo(scoreText.gameObject);
Enter fullscreen mode Exit fullscreen mode

Formatted binding is supported too:

LMotion.Create(0f, 100000f, 1f)
    .BindToText(scoreText, "{0:N2}")
    .AddTo(scoreText.gameObject);
Enter fullscreen mode Exit fullscreen mode

The standard formatted path uses string.Format(), so it can allocate. If this is a hot path, consider the documented ZString integration and profile the full UI update cost.

LitMotion.Animation for Inspector workflows

LitMotion.Animation is a separate package:

https://github.com/annulusgames/LitMotion.git?path=src/LitMotion/Assets/LitMotion.Animation#v2.0.2
Enter fullscreen mode Exit fullscreen mode

When using Git URL dependencies, I prefer pinning both core and animation packages to the same tag explicitly:

{
  "dependencies": {
    "com.annulusgames.lit-motion": "https://github.com/annulusgames/LitMotion.git?path=src/LitMotion/Assets/LitMotion#v2.0.2",
    "com.annulusgames.lit-motion.animation": "https://github.com/annulusgames/LitMotion.git?path=src/LitMotion/Assets/LitMotion.Animation#v2.0.2"
  }
}
Enter fullscreen mode Exit fullscreen mode

It supports Edit Mode and Play Mode previews and is useful for short Prefab-specific animation tuning.

I would still define a team boundary: keep shared duration, ease, and cancellation conventions in code or configuration, and leave only local presentation tuning to the Inspector.

Common production mistakes

A few problems matter more than the exact tween API:

  • Forgetting to bind: LMotion.Create(...).WithEase(...) is only a builder until you call Bind() / BindTo...().
  • Multiple motions write one property: separate ownership or cancel the previous handle.
  • Infinite loops have no shutdown: combine them with object lifetime, a handle, or cancellation.
  • Pause UI stops: use an ignore-time-scale scheduler only where appropriate.
  • "Zero allocation" is interpreted too broadly: closures, strings, async code, and Unity systems still allocate or consume CPU.
  • Everything goes into Sequence: use async/state machines for branching and external waits.

LitMotion Debugger (Window > LitMotion Debugger) is useful for finding duplicate motions and missing cancellation. Use it in Editor Play Mode; profile final CPU/GC with the debugger disabled in a Player build.

Migrating from DOTween

Simple cases are easy to rewrite:

// DOTween
transform.DOMove(targetPosition, 0.5f)
    .SetEase(DG.Tweening.Ease.OutCubic);
Enter fullscreen mode Exit fullscreen mode
// LitMotion
LMotion.Create(transform.position, targetPosition, 0.5f)
    .WithEase(LitMotion.Ease.OutCubic)
    .BindToPosition(transform)
    .AddTo(gameObject);
Enter fullscreen mode Exit fullscreen mode

The real migration risk is behavior around the one-liner. Review:

  • Kill vs cancel behavior;
  • whether cancellation keeps the current value or applies the final value;
  • Sequence behavior;
  • update timing and Time.timeScale;
  • relative tweens;
  • DOTween Pro data;
  • third-party integrations and internal wrappers.

Migrate feature by feature, not library-wide, and never let two tween systems own the same property at the same time.

Adoption checklist

Before standardizing LitMotion in a team, I would verify:

  • Unity/Burst/Collections/Mathematics requirements;
  • custom asmdef references for LitMotion, extensions, UniTask, and TMP;
  • Git dependencies pinned to a tag or commit;
  • current LitMotion Releases and PrimeTween Changelog;
  • representative snippets compiled in a minimal Unity project;
  • third-party assets and internal tooling dependencies;
  • cancellation rules for destruction, reopening, and repeated input;
  • which animations ignore Time.timeScale;
  • Sequence vs async/await conventions;
  • Player-build CPU and GC on target hardware.

Also test cancellation halfway through, object destruction, rapid reversal, and repeated clicks—not only normal completion.

Conclusion

For a new Unity project in 2026 where tween logic is mainly written in C#, LitMotion is the first library I would evaluate.

Its Create -> With -> Bind model is consistent across Transform values, arbitrary data, TextMeshPro, Punch/Shake, Sequence, and UniTask integration. LitMotion v2 also addresses many of the practical features that once made mature alternatives easier to justify by default.

PrimeTween remains a strong alternative, especially if target-oriented APIs fit your team better. DOTween is still the pragmatic choice for projects that already have a working DOTween ecosystem.

Whichever library you choose, the production problems are usually property ownership, re-entry, cancellation, object lifetime, time scale, and the code around the tween—not the number of characters in the tween call.

References

Official docs and source

Releases and benchmarks

Top comments (0)