Library / SDK
djeedai/bevy_tweening avatar
djeedai/bevy_tweening

bevy_tweening: tweening and easing for Bevy components and assets

Tweening animation plugin for the Bevy game engine.

575 stars86 forksRustApache-2.0

At a glance

What is it?
bevy_tweening is a Rust plugin that animates any field of any Bevy component or asset through small lens types, with tween, sequence and delay building blocks and a plugin that does the wiring. It is a small, well-documented crate, and the version numbers in its manifest and its README do not quite agree with each other.
Who is it for?
bevy_tweening fits a Bevy project that needs easing curves, chained timings and completion callbacks on components that are not the built-in Transform, and it fits especially well if you are willing to write a lens for your own types. It is the wrong choice if you want animation with no per-target wiring, because every new field you want to animate needs either a built-in lens or one you write.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The gap is animating your own data, not Transform

Bevy has a transform, and moving an entity around is easy. What is not easy is animating a field of your own component, or a value inside an asset, on a curve, and being told when the animation finished. That is the problem bevy_tweening addresses, and the feature list states it in four checked boxes: animate any field of any component or asset, including custom ones; run multiple tweens per component or asset in parallel; chain multiple tweens one after the other for complex animations; and raise a Bevy event or invoke a one-shot system when an animation completed.

The fourth item is the one that changes how game code is structured. A completion callback means the animation can be a piece of data you hand to the system and then react to, rather than a callback you have to write into the animation closure. In a Bevy app that is the difference between a system that observes an event and one that has to be called from somewhere in the animation's own code path.

The crate is published on crates.io as bevy_tweening with documentation on docs.rs, and the licence in the manifest is MIT OR Apache-2.0, which is the Rust convention of offering the same code under two licences so that a downstream project can pick whichever fits its own licensing.

The repository layout matches a library rather than a game. There is src, docs, benchmarks, examples, a thirdparty directory, a CHANGELOG.md, a release.md file describing the release process, and both LICENSE-APACHE2 and LICENSE-MIT at the root.

Two lines of Cargo and one plugin

Adding the dependency is the whole installation step, and the README's snippet is short enough to copy directly:

toml
[dependencies]
bevy_tweening = "0.16"

The crate has three features and all three are on by default. The bevy_sprite feature includes built-in lenses for some Sprite-related components, bevy_ui does the same for UI-related components, and bevy_text for Text-related components. Each one is a thin wrapper that turns on the matching Bevy features, so a project that only animates transforms can drop them and pull in less of Bevy.

At runtime there is one thing to register, the plugin:

rust
App::default()
    .add_plugins(DefaultPlugins)
    .add_plugins(TweeningPlugin)
    .run();

The README's description of what that buys you is that it provides enough setup for using Bevy Tweening and animating any Bevy built-in or custom component or asset. Animations update as part of the Update schedule of Bevy.

That last point is the one to be aware of. Because the animations advance in the Update schedule, they consume the same frame budget as your own gameplay logic, and a project with many concurrent tweens will feel it in the frame time rather than in the animation itself.

Three building blocks and an operator to combine them

The tweenables are the library's vocabulary, and the README lists three. Tween is a simple tween, an easing animation between two values. Sequence is a series of tweenables executing in series, one after the other. And Delay is a time delay.

Most of them chain with the then operator, and the example is deliberately minimal:

rust
// Produce a sequence executing 'tween1' then 'tween2'
let tween1 = Tween { [...] }
let tween2 = Tween { [...] }
let seq = tween1.then(tween2);

Parallel animation works differently, and the difference is the point. To execute multiple animations in parallel you simply enqueue each animation independently, and the README is blunt about the consequence: this require careful selection of timings.

There is no parallel combinator. A sequence is a first class object that the library advances for you, while two tweens on the same target are two separate entries that each advance on their own clock and that you are responsible for keeping in sync. For animations of fixed length that is easy, and for animations whose duration depends on the value being animated it is not.

The concrete example shows the shape of a single tween. The easing function and the duration come first, and the lens comes with it, carrying the start and end values associated with the animation ratios 0. and 1:

rust
let tween = Tween::new(
    // Use a quadratic easing on both endpoints.
    EaseFunction::QuadraticInOut,
    // Animation time (one way only; for ping-pong it takes 2 seconds
    // to come back to start).
    Duration::from_secs(1),
    // The lens gives the TweenAnimator access to the Transform component,
    // to animate it. It also contains the start and end values associated
    // with the animation ratios 0. and 1.
    TransformPositionLens {
        start: Vec3::ZERO,
        end: Vec3::new(1., 2., -4.),
    },
)

The repeat controls sit on the same builder. with_repeat_count takes a RepeatCount, Finite with a number here, and with_repeat_strategy takes a RepeatStrategy, MirroredRepeat here, which reverses direction after each cycle so the entity moves back and forth.

Lenses are how a field becomes animatable

The lens is the extension point, and the README is explicit that users are encouraged to write their own lens to tailor the animation to their use case.

A lens describes one target field. The naming scheme for the predefined ones is "<TargetName><FieldName>Lens", where the target name is the name of the Bevy component or asset type that is queried by the internal animation system to be modified, and the field name is the field that is mutated in place by the lens. All predefined lenses modify a single field, and custom lenses can be written which modify multiple fields at once.

That is the whole mechanism. The internal system finds the component or asset by type, the lens reads the field, and the lens writes it as the animation ratio advances. Nothing about the animation is specific to a particular field beyond the lens.

The built-in set covers the common cases. For Transform there is TransformPositionLens for the translation, TransformRotationLens for the rotation as a quaternion, and separate lenses for the rotation angle on each axis, TransformRotateXLens and TransformRotateYLens among them. The same pattern applies to the sprite, UI and text components, which is why those are behind feature flags: they exist, but they cost compile time and Bevy features you may not want.

For a common case there is a shortcut. Moving an entity is the most frequent tween by a wide margin, and the README notes that Bevy Tweening provides a shortcut for it, so the same animation can be written as a create-and-queue call on the commands extension:

rust
commands
    // Spawn an entity to animate the position of.
    .spawn((Transform::default(),))
    // Create-and-queue a new Transform::translation animation
    .move_to(
        Vec3::new(1., 2., -4.),
        Duration::from_secs(1),
        EaseFunction::QuadraticInOut,
    );

The general pattern underneath is the same in both forms, which is the point of the shortcut: spawn the entity, create the animation, queue it.

An infinite tween inside a sequence freezes everything after it

The README carries a warning that is worth reading twice, because it is the kind of bug that appears much later than the code that causes it.

Some tweenable animations can be of infinite duration, and the case the README names is RepeatCount::Infinite. If you add such an infinite animation in a sequence and append more tweenables after it, those tweenables will never play because playback will be stuck forever repeating the first animation. You're responsible for creating sequences that make sense. In general, only use infinite tweenable animations alone or as the last element of a sequence.

There is no runtime error for this. The sequence is well formed as a data structure, the animation system advances it faithfully, and the later steps simply never become current. The failure surfaces as an animation that never fires, which in a game often looks like a UI that never opens.

The parallel case has the mirror-image problem, and it is milder. Because parallel animations are independent entries rather than one object, there is no rule that stops you queueing an infinite tween and a finite one on the same target at the same time. They will both advance, and the finite one will finish and write its final value while the infinite one keeps going, so the last writer wins at the end of the animation. That is a race with a deterministic schedule and a surprising outcome.

The version numbers disagree with each other

The manifest and the README are not quite in sync, and that is worth knowing before you file a bug.

The Cargo.toml declares the crate version as 0.16.0, the README asks for bevy_tweening = "0.16", and the crate is on the Bevy main branch tracking badge v0.19. But the manifest's own Bevy dependency is version 0.20, with default-features turned off and the bevy_curve, bevy_asset and bevy_log features enabled. The lens table in the README links to the bevy 0.19 documentation for the Transform component and to docs.rs pages for the lenses built against that same version.

So the crate you install depends on Bevy 0.20, while the documentation it points you at describes Bevy 0.19. In practice the difference is likely to be small, since the lens approach touches components rather than the engine's internals, but it does mean the linked API pages are not guaranteed to match the types in your build.

The manifest also carries the details that matter for a library. The edition is 2021, the authors are François Mockers and Jerome Humbert, and the only runtime dependencies are Bevy itself and thiserror at version 2. The dev-dependency is bevy-inspector-egui at 0.38, which is what the examples use for their on-screen controls.

The examples directory shows what the library is actually for, and the file names say it plainly: ambient_light, colormaterial_color, follow, menu, sequence, sprite_color, text_color, transform_rotation, transform_translation and ui_position, each with a matching gif of the result. There are also run_examples.sh and run_examples.bat scripts at the repository root for launching them.

Editorial conclusion

bevy_tweening fits a Bevy project that needs easing curves, chained timings and completion callbacks on components that are not the built-in Transform, and it fits especially well if you are willing to write a lens for your own types. It is the wrong choice if you want animation with no per-target wiring, because every new field you want to animate needs either a built-in lens or one you write. Verify first which Bevy version your build resolves to, since the manifest requires bevy 0.20 while the README badge and the lens documentation links still point at the 0.19 docs, and pin the crate version rather than tracking the main branch.

Frequently asked questions

What does bevy_tweening do in Bevy?

It is a tweening animation plugin that can animate any field of any component or asset, including your own custom ones, run several animations in parallel, chain them one after the other, and raise a Bevy event or invoke a one-shot system when an animation completes.

How do I add bevy_tweening to a Bevy project?

Add bevy_tweening = "0.16" to the dependencies section of Cargo.toml and register the plugin with .add_plugins(TweeningPlugin) alongside your other plugins. The animations then update as part of Bevy's Update schedule.

What are the three tweenables in bevy_tweening?

Tween is an easing animation between two values, Sequence is a series of tweenables executing one after the other, and Delay is a time delay. Most of them can be chained with the then operator, for example tween1.then(tween2).

How do I animate a custom field with bevy_tweening?

You write a lens for it. The naming scheme is <TargetName><FieldName>Lens, the predefined lenses each modify a single field, and custom lenses can modify multiple fields at once. The README encourages users to write their own lens rather than rely on the built-in set.

What happens if I put an infinite tween in a sequence?

Any tweenable appended after it will never play, because playback stays stuck repeating the infinite animation. The README advises using infinite tweenables alone or as the last element of a sequence.

Official sources

  1. djeedai/bevy_tweening on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/djeedai-bevy-tweening.svg)](https://hysenlabs.com/projects/djeedai-bevy-tweening)

Was this useful?