bevy_tween

repository·main·Indexed 41 days ago

https://github.com/multirious/bevy_tween

A flexible procedural and keyframe animation library for the Bevy engine (v0.13.0). It provides a functional, declarative API for creating complex, composable animations, including parallel and sequential tweens. The system is built on a tree-based architecture using bevy_time_runner, allowing for high flexibility in timing and progress management. It includes built-in interpolators for Bevy transforms, sprites, UI, and assets, as well as a system for emitting tween events.

Tokens
15K
Snippets
49
Records
60
Agent score
47%

What's inside bevy_tween

  1. How `bevy_tween` animation architecture works

    main

    The animation system is built on top of bevy_time_runner, treating an animator as a tree of entities:

    1. Parent (TimeRunner): The parent entity handles all timing and progress management for its children.
    2. Children: Children can be any combination of components, including tweens provided by bevy_tween.
    3. Pipeline: Systems run in a pipeline, communicating through dependency-injected components.

    This tree-based approach allows for high flexibility, enabling parallel, sequential, or overlapping animations of any type, and makes it easy to extend the system with custom components or events.

  2. Create declarative animations with `bevy_tween`

    main

    The bevy_tween crate provides a functional, declarative API for procedural and keyframe animation in Bevy. You can define animations by composing functions that target specific components.

    To create a simple tween, convert an entity ID into a target using .into_target() and use the tween function within commands.animation().insert(...).

    For more complex animations, you can use functional composition (like parallel) to group multiple tweens together. This allows you to decouple animation logic from the spawning logic.

    // Simple tween example
    let sprite_id = commands.spawn(Sprite { ... }).id();
    let sprite = sprite_id.into_target();
    commands.animation()
        .insert(tween(
            Duration::from_secs(1),
            EaseKind::Linear,
            sprite.with(translation(pos0, pos1))
        ));
    
    // Composed parallel animation example
    fn my_animation(
        target: TargetComponent,
        duration: Duration
    ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) {
        parallel((
            tween(duration, EaseKind::QuadraticOut, target.with(translation(...))),
            tween(duration, EaseKind::QuadraticOut, target.with(rotation(...))),
        ))
    }
    
    let sprite_id = commands.spawn(Sprite { ... }).id();
    let sprite = sprite_id.into_target();
    commands.animation().insert(my_animation(sprite, Duration::from_secs(1)))
  3. Run `bevy_tween` examples

    main

    You can explore the capabilities of bevy_tween by running the provided examples. Note that most examples require the bevy/bevy_winit feature to run in a windowed environment.

    • Follow: A square follows a circle with configurable animation. cargo run --example follow -F bevy/bevy_winit
    • Click: Spawns circles on click (or repeatedly on right-click hold). cargo run --example click -F bevy/bevy_winit
    • Hold: Increases effect intensity by holding left click. cargo run --example hold -F bevy/bevy_winit
    • Event: Showcases the tween event feature. cargo run --example event -F bevy/bevy_winit
    • Sprite Sheet: Demonstrates sprite sheet animation. cargo run --example sprite_sheet -F bevy/bevy_winit
    cargo run --example follow -F bevy/bevy_winit
  4. Compose animations using parallel()

    main

    Use parallel() to run multiple animations simultaneously. Every animation in the parallel group receives the same starting position. The total duration of the parallel group is determined by the longest animation within it. You can pass a tuple of animations to parallel() (up to 16 elements via standard tuples, or more by nesting tuples).

    // Example of running animations in parallel
    parallel((
        tween(Duration::from_secs(1), interp1, tween1),
        tween(Duration::from_secs(3), interp2, tween2),
    ));
  5. Configure `bevy_tween` feature gates

    main

    You can enable specific features to add built-in interpolators and systems for different Bevy modules:

    Default Features:

    • bevy_asset: Adds tweening systems for assets.
    • bevy_render: Required by the bevy_sprite feature.
    • bevy_sprite: Adds built-in interpolators for sprites.
    • bevy_ui: Adds built-in interpolators for UI.

    Optional Features:

    • bevy_lookup_curve: Adds interpolation implementation using the bevy_lookup_curve crate.
  6. Manage animation state with sprite.state()

    main

    To avoid manually specifying the starting value of an animation (which can lead to boilerplate if you want a tween to continue from its current value), use the state method. This allows you to use relative interpolators like sprite_color_to instead of absolute ones like sprite_color.

    use bevy_tween::{interpolate::sprite_color_to, combinator::{tween, sequence}};
    
    let sprite = sprite_commands.id().into_target();
    // Initialize the state with the starting value
    let mut sprite_color = sprite.state(WHITE.into()); 
    
    sprite_commands.animation().insert(sequence(( 
        tween(
            Duration::from_secs(1),
            EaseKind::QuadraticOut,
            sprite_color.with(sprite_color_to(RED.into()))
        ),
        tween(
            Duration::from_secs(1),
            EaseKind::QuadraticIn,
            sprite_color.with(sprite_color_to(WHITE.into()))
        ),
    )));
  7. Chain animations using the Combinator framework

    main

    For complex or sequential animations, use the combinator framework. This approach adds a TimeRunner to your entity and creates Tween entities as children to manage the animation lifecycle.

    Common combinators include:

    • tween(...): Creates a single tween animation.
    • sequence(...): Chains multiple animations to run one after another.
    use bevy_tween::{interpolate::sprite_color, combinator::{tween, sequence}};
    
    let sprite = sprite_commands.id().into_target();
    sprite_commands.animation().insert(sequence(( 
        tween(
            Duration::from_secs(1),
            EaseKind::QuadraticOut,
            sprite.with(sprite_color(WHITE.into(), RED.into()))
        ),
        tween(
            Duration::from_secs(1),
            EaseKind::QuadraticIn,
            sprite.with(sprite_color(RED.into(), WHITE.into()))
        ),
    )));
  8. Compose animations using sequence()

    main

    Use sequence() to run multiple animations one after another. Each animation in the sequence starts immediately after the previous one finishes. The total duration of the sequence is the sum of the durations of its components. You can pass a tuple of animations to sequence() (up to 16 elements via standard tuples, or more by nesting tuples).

    // Example of sequencing two animations
    sequence((
        tween(Duration::from_secs(1), my_interpolation, my_tween),
        tween(Duration::from_secs(2), other_interpolation, other_tween),
    ));
  9. Register resource tween systems

    main

    To animate Bevy resources, add a resource tween system to your app. This allows you to interpolate values stored in global resources using an Interpolator.

    use bevy::prelude::*;
    use bevy_tween::prelude::*;
    
    #[derive(Resource)]
    struct ScreenFade(f32);
    
    struct InterpolateScreenFade {
        start: f32,
        end: f32,
    }
    
    impl Interpolator for InterpolateScreenFade {
        type Item = ScreenFade;
    
        fn interpolate(&self, item: &mut Self::Item, value: f32, _previous_value: f32) {
            item.0 = self.start.lerp(self.end, value);
        }
    }
    
    fn main() {
        let mut app = App::new();
    
        // Generic interpolator for resources
        app.add_tween_systems(
            PostUpdate,
            resource_tween_system::<InterpolateScreenFade>(),
        );
    }
  10. Configure the `EaseKindPlugin`

    main

    To enable the automatic sampling of EaseKind components, you must add the EaseKindPlugin to your Bevy application. By default, it registers systems to the PostUpdate schedule. You can customize the schedule using .in_schedule().

    // Standard setup using default PostUpdate schedule
    app.add_plugins(EaseKindPlugin::default());
    
    // Custom setup specifying a different schedule
    app.add_plugins(EaseKindPlugin::in_schedule(MyCustomSchedule));
  11. Install and setup bevy_tween using DefaultTweenPlugins

    main

    To use bevy_tween in your Bevy application, add the DefaultTweenPlugins to your App. By default, these plugins are registered to run in the PostUpdate schedule. You can also specify a custom schedule using .in_schedule(your_schedule).

    use bevy::prelude::*;
    use bevy_tween::prelude::*;
    
    fn main() {
        App::default()
            .add_plugins((DefaultPlugins, DefaultTweenPlugins::default()))
            .run();
    }
  12. Interpolate using LookupCurveHandle and LookupCurveCache

    main

    To perform interpolation using a lookup curve, attach the following components to your entity:

    1. LookupCurveHandle(Handle<LookupCurve>): A wrapper around the Bevy handle for your curve asset.
    2. TimeSpanProgress: (Provided by bevy_tween) Tracks the current progress of the tween.
    3. LookupCurveCache(LookupCache) (Optional): A wrapper for LookupCache that enables cached lookups for better performance. If this component is missing, the system will fall back to standard lookups.

    The sample_lookup_curve_system will automatically detect changes to the handle or progress and insert a TweenInterpolationValue component onto the entity with the sampled value.

    use bevy::prelude::*;
    use bevy_tween::interpolation::bevy_lookup_curve::{LookupCurveHandle, LookupCurveCache};
    // Assuming LookupCurve and TimeSpanProgress are available via imports
    
    fn setup_tween_entity(mut commands: Commands, curve_handle: Handle<LookupCurve>) {
        commands.spawn((
            LookupCurveHandle(curve_handle),
            LookupCurveCache(LookupCache::default()), // Optional for performance
            // TimeSpanProgress component must also be present for the system to run
        ));
    }