Giter Club home page Giter Club logo

bevy_tweening's Introduction

๐Ÿƒ Bevy Tweening

License: MIT/Apache Doc Crate Build Status Coverage Status Bevy tracking

Tweening animation plugin for the Bevy game engine.

Features

  • Animate any field of any component or asset, including custom ones.
  • Run multiple tweens (animations) per component/asset in parallel.
  • Chain multiple tweens (animations) one after the other for complex animations.
  • Raise a Bevy event or invoke a callback when an tween completed.

Usage

Dependency

Add to Cargo.toml:

[dependencies]
bevy_tweening = "0.11"

This crate supports the following features:

Feature Default Description
bevy_asset Yes Enable animating Bevy assets (Asset) in addition of components.
bevy_sprite Yes Includes built-in lenses for some Sprite-related components.
bevy_ui Yes Includes built-in lenses for some UI-related components.
bevy_text Yes Includes built-in lenses for some Text-related components.

System setup

Add the TweeningPlugin to your app:

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

This provides the basic setup for using ๐Ÿƒ Bevy Tweening. However, additional setup is required depending on the components and assets you want to animate:

  • To ensure a component C is animated, the component_animator_system::<C> system must run each frame, in addition of adding an Animator::<C> component to the same Entity as C.

  • To ensure an asset A is animated, the asset_animator_system::<A> system must run each frame, in addition of adding an AssetAnimator<A> component to any Entity. Animating assets also requires the bevy_asset feature (enabled by default).

By default, ๐Ÿƒ Bevy Tweening adopts a minimalist approach, and the TweeningPlugin will only add systems to animate components and assets for which a Lens is provided by ๐Ÿƒ Bevy Tweening itself. This means that any other Bevy component or asset (either built-in from Bevy itself, or custom) requires manually scheduling the appropriate system.

Component or Asset Animation system added by TweeningPlugin?
Transform Yes
Sprite Only if bevy_sprite feature
ColorMaterial Only if bevy_sprite feature
Style Only if bevy_ui feature
Text Only if bevy_text feature
All other components No

To add a system for a component C, use:

app.add_systems(Update, component_animator_system::<C>.in_set(AnimationSystem::AnimationUpdate));

Similarly for an asset A, use:

app.add_systems(Update, asset_animator_system::<A>.in_set(AnimationSystem::AnimationUpdate));

Animate a component

Animate the transform position of an entity by creating a Tween animation for the transform, and adding an Animator component with that tween:

// Create a single animation (tween) to move an entity.
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 Animator 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.),
    },
)
// Repeat twice (one per way)
.with_repeat_count(RepeatCount::Finite(2))
// After each iteration, reverse direction (ping-pong)
.with_repeat_strategy(RepeatStrategy::MirroredRepeat);

commands.spawn((
    // Spawn a Sprite entity to animate the position of.
    SpriteBundle {
        sprite: Sprite {
            color: Color::RED,
            custom_size: Some(Vec2::new(size, size)),
            ..default()
        },
        ..default()
    },
    // Add an Animator component to control and execute the animation.
    Animator::new(tween),
));

Chaining animations

Bevy Tweening supports several types of tweenables, building blocks that can be combined to form complex animations. A tweenable is a type implementing the Tweenable<T> trait.

  • Tween - A simple tween (easing) animation between two values.
  • Sequence - A series of tweenables executing in series, one after the other.
  • Tracks - A collection of tweenables executing in parallel.
  • Delay - A time delay.

Most tweenables can be chained with the then() operator:

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

Predefined Lenses

A small number of predefined lenses are available for the most common use cases, which also serve as examples. Users are encouraged to write their own lens to tailor the animation to their use case.

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

Bevy Components

Target Component Animated Field Lens Feature
Transform translation TransformPositionLens
rotation (Quat)ยน TransformRotationLens
rotation (angle)ยฒ TransformRotateXLens
rotation (angle)ยฒ TransformRotateYLens
rotation (angle)ยฒ TransformRotateZLens
rotation (angle)ยฒ TransformRotateAxisLens
scale TransformScaleLens
Sprite color SpriteColorLens bevy_sprite
Style position UiPositionLens bevy_ui
BackgroundColor UiBackgroundColorLens bevy_ui
Text TextStyle::color TextColorLens bevy_text

ยน Shortest-path interpolation between two rotations, using Quat::slerp().

ยฒ Angle-based interpolation, valid for rotations over ยฝ turn.

See the comparison of rotation lenses for details.

Bevy Assets

Asset animation always requires the bevy_asset feature.

Target Asset Animated Field Lens Feature
ColorMaterial color ColorMaterialColorLens bevy_asset + bevy_sprite

Custom lens

A custom lens allows animating any field or group of fields of a Bevy component or asset. A custom lens is a type implementing the Lens trait, which is generic over the type of component or asset.

struct MyXAxisLens {
    start: f32,
    end: f32,
}

impl Lens<Transform> for MyXAxisLens {
    fn lerp(&mut self, target: &mut Transform, ratio: f32) {
        let start = Vec3::new(self.start, 0., 0.);
        let end = Vec3::new(self.end, 0., 0.);
        target.translation = start + (end - start) * ratio;
    }
}

Note that the lens always linearly interpolates the field(s) of the component or asset. The type of easing applied modifies the rate at which the ratio parameter evolves, and is applied before the lerp() function is invoked.

The basic formula for lerp (linear interpolation) is either of:

  • start + (end - start) * scalar
  • start * (1.0 - scalar) + end * scalar

The two formulations are mathematically equivalent, but one may be more suited than the other depending on the type interpolated and the operations available, and the potential floating-point precision errors.

Custom component support

Custom components are animated via a lens like the ones described in Bevy Components.

#[derive(Component)]
struct MyCustomComponent(f32);

struct MyCustomLens {
    start: f32,
    end: f32,
}

impl Lens<MyCustomComponent> for MyCustomLens {
    fn lerp(&mut self, target: &mut MyCustomComponent, ratio: f32) {
        target.0 = self.start + (self.end - self.start) * ratio;
    }
}

Then, in addition, the system component_animator_system::<CustomComponent> needs to be added to the application, as described in System Setup. This system will extract each frame all CustomComponent instances with an Animator<CustomComponent> on the same entity, and animate the component via its animator.

Custom asset support

The process is similar to custom components, creating a custom lens for the custom asset. The system to add is asset_animator_system::<CustomAsset>, as described in System Setup. This requires the bevy_asset feature (enabled by default).

Examples

See the examples/ folder.

cargo run --example menu --features="bevy/bevy_winit"

menu

cargo run --example sprite_color --features="bevy/bevy_winit"

sprite_color

cargo run --example transform_rotation --features="bevy/bevy_winit"

sprite_color

cargo run --example transform_translation --features="bevy/bevy_winit"

sprite_color

cargo run --example colormaterial_color --features="bevy/bevy_winit"

colormaterial_color

cargo run --example ui_position --features="bevy/bevy_winit"

ui_position

cargo run --example sequence --features="bevy/bevy_winit"

sequence

Ease Functions

Many ease functions are available:

  • QuadraticIn
  • QuadraticOut
  • QuadraticInOut
  • CubicIn
  • CubicOut
  • CubicInOut
  • QuarticIn
  • QuarticOut
  • QuarticInOut
  • QuinticIn
  • QuinticOut
  • QuinticInOut
  • SineIn
  • SineOut
  • SineInOut
  • CircularIn
  • CircularOut
  • CircularInOut
  • ExponentialIn
  • ExponentialOut
  • ExponentialInOut
  • ElasticIn
  • ElasticOut
  • ElasticInOut
  • BackIn
  • BackOut
  • BackInOut
  • BounceIn
  • BounceOut
  • BounceInOut

Compatible Bevy versions

The main branch is compatible with the latest Bevy release.

Compatibility of bevy_tweening versions:

bevy_tweening bevy
0.11 0.14
0.10 0.13
0.9 0.12
0.8 0.11
0.7 0.10
0.6 0.9
0.5 0.8
0.4 0.7
0.2-0.3 0.6
0.1 0.5

Due to the fast-moving nature of Bevy and frequent breaking changes, and the limited resources to maintan ๐Ÿƒ Bevy Tweening, the main (unreleased) Bevy branch is not supported. However the bevy_tweening crate is upgraded shortly after each new bevy release to support the newly released version.

Comparison with bevy_easings

The bevy_tweening library started as a fork of the bevy_easings library by Franรงois Mocker, with the goals to:

  • explore an alternative design based on lenses instead of generic types for each easer/animator. This reduces both the number of generic types needed, and hopefully the code size, as well as the number of systems needed to perform the interpolation.
  • improve the interpolation of assets to avoid creating many copies like bevy_easings does, and instead mutate the assets (and, by similarity, the components too) in-place without making a copy. The in-place mutation also allows a more optimal interpolation limited to modifying the fields of interest only, instead of creating a new copy of the entire component each tick.

bevy_tweening's People

Contributors

arendjr avatar dgsantana avatar djee-ms avatar djeedai avatar enaut avatar guyguy2001 avatar gyrobifastigium avatar iancormac84 avatar peepo-juice avatar shuoli84 avatar sjael avatar supercilex avatar wainwrightmark avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

bevy_tweening's Issues

Support for MorphWeights

Are MorphWeights supported? I am trying to implement lerping between morph weights (or blendshapes as I call them since I came from working in unity) but from I can tell the lens never gets called.

I am also still really new to bevy and rust in general.

Here is what I currently have.

        commands.spawn((
            SceneBundle {
                scene: asset_server.load("Nukude.glb#Scene0"),
                ..default()
            },
            Animator::new(Dummy::<MorphWeights>::new()),
            AnimatorState::Sad,
            MetaData {
                mesh: asset_server.load("Nukude.glb#Mesh0/Primitive0"),
                material: asset_server.load("Nukude.glb#Material0"),
                blendshapes: Vec::new()
            },
            Face,
        ));
    fn check_blink_status(mut query: Query<(&mut Animator<MorphWeights>, &MetaData), With<Face>>,
                          time: Res<Time>,
                          mut timer: ResMut<BlinkTimer>) {
        if timer.tick(time.delta()).finished() {
            let mut rng = rand::thread_rng();
            for (mut animator, meta_data) in &mut query {
                let index = utils::get_index(&meta_data.blendshapes, "Blink");
                let tween_close = Tween::new(
                    EaseFunction::SineInOut,
                    Duration::from_secs_f64(0.25),
                    BlendshapeLens {
                        index,
                        start: 0.0,
                        end: 1.0
                    }
                );
                let tween_open = Tween::new(
                    EaseFunction::SineInOut,
                    Duration::from_secs_f64(0.25),
                    BlendshapeLens {
                        index,
                        start: 1.0,
                        end: 0.0
                    }
                );
                animator.set_tweenable(tween_close.then(tween_open));
                timer.0 = Timer::from_seconds(rng.gen_range(3..30) as f32, TimerMode::Once)
            }
        }
    }

My custom lens for MorphWeights

use bevy::prelude::MorphWeights;
use bevy_tweening::Lens;

#[derive(Debug, Copy, Clone, PartialEq)]
pub struct BlendshapeLens {
    pub index: usize,
    pub start: f32,
    pub end: f32
}

impl Lens<MorphWeights> for BlendshapeLens {
    fn lerp(&mut self, target: &mut MorphWeights, ratio: f32) {
        target.weights_mut()[self.index] = self.start * (1.0 - ratio) + self.end * ratio;
        println!("{}", target.weights_mut()[self.index]);
    }
}

I added the println as a way to test if it is getting called but as far as I can tell it isn't.

Here is example of elsewhere where I manually manipulate them in code. I have MorphWeights as it's own query because it was causing the other query to not pull any data.

    fn play_expression(mut query: Query<(&mut Animator<MorphWeights>, &AnimatorState, &MetaData), Changed<AnimatorState>>,
                       mut morph_weights: Query<&mut MorphWeights>,
                       mut materials: ResMut<Assets<StandardMaterial>>,
    ) {
        if query.is_empty() { return; }
        let (mut animator, state, meta_data) = query.single_mut();

        // Clear the blendshapes before ww set the new expression
        let mut morphs = morph_weights.single_mut();
        let blendshapes = morphs.weights_mut();
        blendshapes[utils::get_index(&meta_data.blendshapes, "MoveEye")] = 1.0;
        blendshapes[utils::get_index(&meta_data.blendshapes, "HideBlush")] = 1.0;
        blendshapes[utils::get_index(&meta_data.blendshapes, "Frown")] = 0.0;
        blendshapes[utils::get_index(&meta_data.blendshapes, "Doubt")] = 0.0;
        blendshapes[utils::get_index(&meta_data.blendshapes, "Surprised")] = 0.0;
        blendshapes[utils::get_index(&meta_data.blendshapes, "Sadness")] = 0.0;
        blendshapes[utils::get_index(&meta_data.blendshapes, "Anger")] = 0.0;

I am working on some cosplay software that takes a face model and renders it to some LED matrices. Looking at other animation options because the default AnimationClips override what I set for the blendshapes and I plan to implement visemes as well.

Translation values still being changed after Animation played "Once" finished

Whenever I use the TransformPositionLens with TweeningType::Once, even after the animation is completed, the entitities transform seems to be "changed" still. I know this because I have another system listening on transform Change and it is triggering every frame.

#[derive(Component)]
pub struct ImTweeningHere;

pub fn move_player_on_click(
    mut commands: Commands,
    mouse_input: Res<Input<MouseButton>>,
    grid_dimensions: Res<GridDimensions>,
    mouse_map_pos: Option<Res<MouseMapPos>>,
    player: Query<(Entity, &Transform), (With<Player>, Without<ImTweeningHere>)>,
) {
    if let (true, Some(mouse_map_pos)) = (
        mouse_input.just_pressed(MouseButton::Left),
        mouse_map_pos.as_deref(),
    ) {
        if let Ok((entity, player_transform)) = player.get_single() {
            let end_position = grid_dimensions.grid_loc_to_vec2(
                (mouse_map_pos.0.x / grid_dimensions.tile_area).floor(),
                (mouse_map_pos.0.y / grid_dimensions.tile_area).floor(),
            );

            let tween = Tween::new(
                EaseFunction::CircularInOut,
                TweeningType::Once,
                std::time::Duration::from_millis(500),
                TransformPositionLens {
                    start: player_transform.translation,
                    end: Transform::from_translation(end_position.extend(player_transform.translation.z))
                        .translation,
                },
            )
            .with_completed_event(true, 0);

            let animator = Animator::new(tween);
            commands
                .entity(entity)
                .insert(animator)
                .insert(ImTweeningHere);
        }
    }
}

the above system triggers the animation on map click, when the animation completes, the entities Transform still fires off

fn player_transforming(
    player: Query<&Transform, (With<Player>, Changed<Transform>)>,
) {
    if let Ok(transforming) = player.get_single() {
        info!("player is transforming!: {transforming:?}");
    }
}

as a work around, I manually remove the AnimatorTransform on tween complete

fn movement_completed(mut commands: Commands, mut on_tween_complete: EventReader<TweenCompleted>) {
    for evt in on_tween_complete.iter() {
        if evt.user_data == 0 {
            commands
                .entity(evt.entity)
                .remove::<ImTweeningHere>()
                .remove::<Animator<Transform>>();
        }
    }
}

However, I am not sure if this is the intended behavior. do we need to remove the Animator on animation complete. I expect that the intended behavior when using a TweeningType of Once is the component will be removed on animation complete for the entity and we use the TweenCompleted event for other stuff, like my use case here to remove my own component.

Adding new tweens to existing ones

Let's say I have an entity that is set up with a tween targeting the entity's transform.rotation. The tween loops infinitely throughout the game.

But I also want to have a system that can insert a new tween that targets said entity's transform.scale, depending on the state of the game. The tween loops only once. I also want to be able to replay that tween, depending on the game state.

The problem is that this requires you to put more than one Animator<Transform> component on the entity, which is currently not possible in Bevy. Is there a good solution for this use-case?

Allow tweenables to play backward

Allow all Tweenable to play backward once or looped, or to start from end in ping-pong mode.

Also allow this direction to be changed at runtime at any point, to allow some animation to "go back to start" without having to create a new Tweenable which encodes the reverse animation explicitly.

Note: this is a prerequisite for #16

Design what happens to completion events when playback jumps backward

The set_progress() (and discussed set_elapsed()) method allow you to move the animation to an earlier point than the one it's in currently. Traditionally for tick-based animation the tween will emit a completion event each time it's completed. However, when playback jumps backward, it's unclear what should be done for completion events, especially when the tween is repeating, and jumping backward can decrease the completed count (times_completed) by more than one.

Conceptually it feels like jumping backward this way is not a natural way of animating, and is more geared toward editors or manual seeking, so it feels like ignoring completion events (and callbacks) in that case is the best approach.

Note however that there is also the playback direction which allows smooth playback but in reverse order. Although this is a distinct and probably orthogonal feature (it only swaps the endpoints of the lens), conceptually it can be confusing to make a distinction between "moving by 1 second in backward direction" and "seeking 1 second before the current point", which both result in the same state for the underlying animated component, but not for the tween which has a different direction.

Make `Sequence` loopable

This is a sub-issue of #8. Opening this separately because for Sequence the problem is I believe well-defined, and work can move forward. This is not the case for Tracks though, so splitting the issue in two for clarity.

Allow Sequence to be loopable both with TweeningType::Loop (play all child tweenables once, then restart at first one) and with TweeningType::PingPong (play all child tweenables once, then restart from last back to first, then restart from first to last, etc.).

Add a `prelude`

Would be more explicit and consistent with other Bevy ecosystem crates. use bevy_tweening::*; may pull in unwanted names.

The 'custom component' description is misleading

As noted on discord, the the custom component part of the readme is a bit misleading and a common source of confusion. The readme should probably be more explicit about the fact that not all built-in components are supported (and probably just link to the plugin source as both an example of adding the system and to show which types are added by default).

Add example for tick()

Could you add an example for calling tick()?

I have no idea what Targetable needs:
tween.tick(Duration::ZERO, ???, e, &mut writer);

Tween `ResMut` resources

Is there a way to use resources (ResMut) as a target for the tween? Would open up a lot of nice possibilities.

Working with `bevy_vector_shapes`

Is there nice a way to use this plugin in arbitrary systems? I wanted to use this plugin with bevy_vector_shapes:

fn draw(mut painter: ShapePainter) {
    // Draw a circle
    painter.circle(100.0);
}

But since bevy_tweening works by targeting specific components using lenses, I can't figure out an ergonomic way of making it work together.

Any ideas?

how to remove animator from entity

As far as i can see the Animator<> is generic over the Tweenable and that becomes very hairy and bespoke for each tween generated to be able to remove the whole Animator again from an entity.

What is the best practice here?

Add speed test ticking a tween

Add an animator speed test which actually ticks a tween. Currently because the ticking is deferred to the tween itself, and applied "manually" by the built-in animator systems, it's easy to break the feature without noticing as the systems are also not tested.

Multiply tweens to a single entity

Is it possible to apply multiply tweens to a single entity? Like TransformPositionLens + TransformRotateYLens. Or should I create a custom lens in this case?

Improve capabilities of modifying Tweenables after creation

I have implemented a drag and drop system for Sprite and would like to build on this by having sprite ease back towards its original position after being dropped.

Here I came across some limitations of bevy_tweening.

For this I query Animator<Transform> in a system that checks for the Sprite position and whether it has been dropped. Today, there seems to be no way to update the TransformationPositionLens of the Tween of that Animator with a new start value, or change other fields of the Tween (as they are private).

The only way to get this working that I found is to use the set_tweenable method on the Animator to set a new Tween. I am not sure about performance impact, but it seems not an ideal way to do things. This also means that the Tween originally defined for this Animator is just unused boilerplate.

I would like to define an initial Tween and then change some of its parameters in a system call, without having to instantiate a new Tween.

I donโ€™t know if this is in the scope of this crate or if there are technical reasons this is not possible, but this seems like an area where improved ergonomics would make bevy_tweening even more useful.

[Support?] How to animate complex shapes involving modifying a path, position, and rotation with multiple steps?

Hey,

I want to create a turtlegraphics clone in bevy. So far I achieved the basic functionality:

2022-08-26.10-02-58.mp4

I did so by one line/circle etc each being one bundle with an animator...

Now I would like to make this form filled: like here https://github.com/sunjay/turtle/blob/master/docs/assets/images/examples/ferris.gif

But to fill that all the lines need to be in one bundle I think. Of course I could create a rather complicated lens that draws that bundle but the lerping would break because all the steps in the middle would be faster than at the beginning or the end...

I'm not sure if I found all the relevant documentation so I might miss something obvious. However I'd very much appreciate some hints on how such a thing could be achieved.

Thank you!

Support Time<Real> (in bevy 0.12)

This's is just a prep issue for the upcoming bevy 0.12 release.
Time scaling was added in bevyengine/bevy#8964 with Time<()> being the default. In normal systems that's Time<Virtual> (scaled) which makes sense, but it would be nice to be able to animate in unscaled time (Time<Real>) (e.g. for a pause UI).

Adding a field to Animator like use_real_time: bool and adding the appropriate builder functions might do the trick for the API.

Circle rotation results in invisible entity

Here is the code:

use bevy::prelude::*;
use bevy_tweening::{lens::*, *};

fn main() {
    App::default()
        .add_plugins(DefaultPlugins)
        .add_plugin(TweeningPlugin)
        .add_startup_system(setup)
        .run();
}

fn setup(mut commands: Commands) {
    commands.spawn_bundle(OrthographicCameraBundle::new_2d());

    let size = 80.;
    let tween = Tween::new(
        EaseMethod::Linear,
        TweeningType::PingPong,
        std::time::Duration::from_secs(1),
        TransformRotationLens {
            start: Quat::IDENTITY,
            end: Quat::from_axis_angle(Vec3::Z, std::f32::consts::PI),
        },
    );

    commands
        .spawn_bundle((
            Transform::default(),
            GlobalTransform::default(),
        ))
        .with_children(|parent| {
            parent
                .spawn_bundle(SpriteBundle {
                    sprite: Sprite {
                        color: Color::RED,
                        custom_size: Some(Vec2::new(size, size * 0.5)),
                        ..Default::default()
                    },
                    ..Default::default()
                })
                .insert(Animator::new(tween));
        });
}

It works correctly, sprite rotates. But if you replace std::f32::consts::PI with std::f32::consts::TAU the sprite becomes invisible.

Tracks should allow tweens with different lens type

Basically what I want to do is to animate a simple text to move up and at the same time have a color fade. I believe this code should work:

let tracks = Tracks::new([
    Tween::new(
        EaseFunction::QuadraticIn,
        Duration::from_secs(1),
        TransformPositionLens {
            start: Vec3::new(40.0, 0.0, 1.0),
            end: Vec3::new(40.0, 15.0, 1.0),
        },
    )
    .with_completed_event(1),
    Tween::new(
        EaseFunction::QuadraticIn,
        Duration::from_secs(1),
        TextColorLens {
            section: 0,
            start: Color::BLACK,
            end: Color::Rgba {
                red: 0.0,
                green: 0.0,
                blue: 0.0,
                alpha: 0.0,
            },
        },
    ),
]);

But this gives me this error: expected struct Tween<Transform> found struct Tween<bevy::prelude::Text>

Bevy Event/Callback Example

The docs show 4 features front and center for this crate.


The first three are all well covered by the examples you have, but

Raise a Bevy event or invoke a callback when an tween completed

is not. At least one example per major feature would be nice :)

Investigate providing `&mut world` in tweenable callback vs. using events

Currently a tweenable callback has little information. It contains a reference to the Tween<T>, and (change not merged yet) the Entity of the Animator<T> or AssetAnimator<T>.

Kero on Discord suggested providing a &mut world to allow the callback to do something useful, similar to what bevy-tick-timers does. But this forces the system ticking the animators to take a global lock and become exclusive, serializing execution and being a potential choke point for other Bevy systems, which can cause performance issues. This is particularly bad for bevy_tweening which has a series of such systems, once per component type:

pub fn component_animator_system<T: Component>(
time: Res<Time>,
mut query: Query<(&mut T, &mut Animator<T>)>,
) {
for (ref mut target, ref mut animator) in query.iter_mut() {
if animator.state != AnimatorState::Paused {
if let Some(tweenable) = animator.tweenable_mut() {
tweenable.tick(time.delta(), target);
}
}
}
}

A possible alternative which sounds more Bevy-esque would be to replace the callback with a Bevy event. This way the user app can design any system they want to read that event, with any resource / query they need. This however implies being able to write an event without knowing its type at build time, to avoid depending on the event type as an extra generic parameter for the system:

let mut tween = Tween::new(...);
tween.set_completed::<MyEvent>();

then

pub fn component_animator_system<T: Component>( 
    time: Res<Time>, 
    mut query: Query<(&mut T, &mut Animator<T>)>,
    mut writer: EventWriter<????>, //< Here we don't know the type
) { 
    for (ref mut target, ref mut animator) in query.iter_mut() { 
        if animator.state != AnimatorState::Paused { 
            if let Some(tweenable) = animator.tweenable_mut() { 
                tweenable.tick(time.delta(), target, writer); 
            } 
        } 
    } 
} 

ColorMaterialColorLens bug

bevy::prelude::ColorMaterial does not implement bevy::prelude::Component, which breaks ColorMaterialColorLens.

Full error message:

error[E0277]: the trait bound bevy::prelude::ColorMaterial: bevy::prelude::Component is not satisfied
--> src\main.rs:265:44
|
265 | commands.entity(e2).insert(Animator::new(Tween::new(
| ^^^^^^^^ the trait bevy::prelude::Component is not implemented for bevy::prelude::ColorMaterial
|
= help: the following other types implement trait bevy::prelude::Component:
AdditionalMassProperties
AlphaMode
AnimationPlayer
Animator
AssetAnimator
BackgroundColor
Ball
BloomSettings
and 120 others
note: required by a bound in Animator
--> {DIRECTORY}\bevy_tweening-0.6.0\src\lib.rs:408:24
|
408 | pub struct Animator<T: Component> {
| ^^^^^^^^^ required by this bound in Animator

Sequence with repeat_count

Hey,

I was trying to have an object zoom into existence then wiggle infinitely... I tried doing that with:

Tween::new(
            EaseFunction::QuadraticInOut,
            Duration::from_millis(1000),
            TransformScaleLens {
                start: Vec3::ZERO,
                end: Vec3::ONE,
            },
        )
        .then(
            Tween::new(
                EaseFunction::QuadraticInOut,
                Duration::from_millis(200),
                TransformRotateZLens {
                    start: 0.05,
                    end: -0.05,
                },
            )
            .with_repeat_strategy(bevy_tweening::RepeatStrategy::MirroredRepeat)
            .with_repeat_count(RepeatCount::Infinite),
        )

But when Running the first animation works well, however the second one panics with:

thread 'Compute Task Pool (13)' panicked at 'overflow when subtracting durations', library/core/src/time.rs:930:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

The same happens if repeat_count is set to a finite number even when set to 1.

It might be that this is "known" or "intended" but if so it should probably be in the documentation for the then method or the Sequence type.

The Behaviour I expected would have been an infinite sequence first the zoom then forever wiggling...

Completion events on all Tweenables

set_completed_event/with_completed_event are currently only available on the Tween type. It'd be nice to have them on any Tweenable, but especially on the compound ones: Sequence and Tracks.

My current workaround for this is to attach the completion to the last tweenable in sequence. This is pretty awkward, because you have to do it before you type-erase it, e.g.:

// `transitions` is Vec<(Duration, /* target alpha */ f32)>
let mut sequence: Vec<BoxedTweenable<Sprite>> = Vec::with_capacity(transitions.len());

let mut current_alpha = initial;
for (i, &(duration, end_alpha)) in transitions.iter().enumerate() {
    if (end_alpha - current_alpha).abs() < 0.01 {
        sequence.push(Box::new(Delay::new(duration)));
    } else {
        let lens = SpriteColorLens {
            start: fade_sprite_color(current_alpha),
            end: fade_sprite_color(end_alpha),
        };

        let mut tween = Tween::new(EaseMethod::Linear, TweeningType::Once, duration, lens);
        if i == transitions.len() - 1 {
            tween.set_completed_event(TWEEN_COMPLETION_COOKIE);
        }

        sequence.push(Box::new(tween));
    }
    current_alpha = end_alpha;
}

Sequence::new(sequence)

This also doesn't work if the last tweenable is Delay. For that case, you need to turn your Sequence into Tracks, with a parallel Tween that does nothing but provide the completion event. This might require a custom NoopLens<T: Component> type.

All in all, pretty messy. Having set_completed_event on more tweenable types would eliminate the need for tricks like this.

Use handle from component for asset animation (?)

MaterialMesh2dBundle<T> has a material: Handle<T> component, and this can be used to animate without needing to pass the handle to the animator. I implemented this in my own project and it seems to work although perhaps there are some implications I am not aware of?

Code:

#[derive(Component)]
pub struct AssetComponentAnimator<T: Asset> {
    pub state: AnimatorState,
    tweenable: BoxedTweenable<T>,
    speed: f32,
}

impl<T: Asset> AssetComponentAnimator<T> {
    pub fn new(tweenable: impl Tweenable<T> + 'static) -> Self {
        Self {
            state: AnimatorState::Playing,
            tweenable: Box::new(tweenable),
            speed: 1.0,
        }
    }

    pub fn speed(&self) -> f32 {
        self.speed
    }

    pub fn tweenable_mut(&mut self) -> &mut dyn Tweenable<T> {
        self.tweenable.as_mut()
    }
}

pub struct AssetComponentTarget<'a, T: Asset> {
    assets: ResMut<'a, Assets<T>>,
    pub handle: Handle<T>,
}

impl<'a, T: Asset> AssetComponentTarget<'a, T> {
    pub fn new(assets: ResMut<'a, Assets<T>>) -> Self {
        Self {
            assets,
            handle: Handle::weak(HandleId::default::<T>()),
        }
    }

    pub fn is_valid(&self) -> bool {
        self.assets.contains(&self.handle)
    }
}

impl<'a, T: Asset> Targetable<T> for AssetComponentTarget<'a, T> {
    fn target_mut(&mut self) -> &mut T {
        self.assets.get_mut(&self.handle).unwrap()
    }
}

pub fn asset_component_animator_system<T: Asset>(
    time: Res<Time>,
    assets: ResMut<Assets<T>>,
    mut query: Query<(Entity, &Handle<T>, &mut AssetComponentAnimator<T>)>,
    events: ResMut<Events<TweenCompleted>>,
) {
    let mut events: Mut<Events<TweenCompleted>> = events.into();
    let mut target = AssetComponentTarget::new(assets);
    for (entity, handle, mut animator) in query.iter_mut() {
        if animator.state != AnimatorState::Paused {
            target.handle = handle.clone();
            let speed = animator.speed();
            animator.tweenable_mut().tick(
                time.delta().mul_f32(speed),
                &mut target,
                entity,
                &mut events,
            );
        }
    }
}

Animating sprite sheets

First of all. Thanks for this repo. I just discovered it and it's really well made and saving me a lot of time.

It feels natural to use this to tick the index of a TextureAtlas so I gave it a thought. I'm coming up on some loose ends. Here's what I can gauge:

  • Using Tween to tick the TextureAtlas doesn't feel right since each frame of the index could take varying amounts of time. Tween only takes one duration.
  • String together Tween into Sequence is verbose, and loses some of the functionality of Tween, such as the ability to repeat the sequence as a whole or sending events on completion.
  • It feels natural to use EaseMethod::Discrete for this purpose but the value returned by progress is exclusive, hence you're limited to doing something like EaseMethod::Discrete(0.999), which isn't that bad but feels hacky. This is what I'm coming up against if I were to choose to implement a custom Lens.

Any thoughts on how to use it to tick sprite sheet indices? Is this something you could support?

Bevy 0.11 support

This crate is fantastic. It would be nice to support most recent version of Bevy

Repeat broken after first iteration

Run any example (except menu currently broken due to #41) : the color animation goes from red to blue smoothly during the first loop, then from the second loop ping-pongs between full red and full blue without any interpolation.

@SUPERCILEX this is probably due to a bug in #19.

This is blocking #34

`Sequence::tick` accumulates timing error whenever a tween completes

let tween_state = tween.tick(delta, target, entity, event_writer);

Sequence::tick spends its entire delta time on its current tween with no carry-over. If for example delta is 16ms and there are only 2ms left to complete the current tween, then the current tween will get ticked for the entire 16ms and the next tween will start from zero instead of starting 14ms in. Every time the Sequence crosses a tween boundary, the error accumulates and the sequence drifts further and further from where it should be.

Extend Tracks and Sequence functionality

To combine multiply tweens and loop them I have to create custom lens which is not very ergonomic. We currently have Tracks and Sequence, but they aren't loopable.
I would like to request to extend their functionality. It would be great if we could combine multiply tweens with different tweening type. For example, to animate healing pickup like in Owerwatch I need rotation tween with Loop and translation tween with PingPong. Video of how such pickups look.

Support Bevy 0.9

Admitted โ€“ this issue is not very original, but it does not hurt to have it filed: Bevy 0.9 has just been released, and there is a migration guide at https://bevyengine.org/learn/book/migration-guides/0.8-0.9/

I am still inexperienced with rust and cargo and run into the most basic of problems: bevy_tweening 0.5.0 still depends on bevy 0.8.1, and I am getting compilation errors related to mismatching dependency versions.

Custom lens not working

Hi, i was trying to implement simple custom lens for animating my ui image alpha, but it doesn't work, am i doing something wrong ?

struct ImageOpacityLens {
    start: f32,
    end: f32,
}

impl Lens<BackgroundColor> for ImageOpacityLens {
    fn lerp(&mut self, target: &mut BackgroundColor, ratio: f32) {
        let interpolated_value = self.start + (self.end - self.start) * ratio;
        target.0.set_a(interpolated_value);
    }
}
let tween = Tween::new(
        EaseFunction::QuadraticInOut,
        Duration::from_secs(2),
        ImageOpacityLens {
            start: 0.0,
            end: 1.0,
        },
    )
    .with_repeat_count(RepeatCount::Finite(2))
    .with_repeat_strategy(RepeatStrategy::MirroredRepeat);

commands.spawn((
        ImageBundle {
            style: Style {
                margin: UiRect::all(Val::Auto),
                size: Size::new(Val::Px(300.0), Val::Auto),
                ..default()
            },
            image: UiImage(icon),
            ..default()
        },
        Animator::new(tween),
    ));

Avoid unnecessary change detection resulting from component lens

Right now the Lens trait's lerp method takes &mut T for T: Component, but it should only need Mut<T>.

The system invokes DerefMut so we have no chance to avoid change detection in a custom lens:

&mut target,

Specifically, I have a custom lens that acts like a discrete switch that changes from one value to another at a specific ratio. This would only require a single DerefMut, but in fact it happens every frame that the Animator is running.

How to tween both translation and scale of Transform simultaneously?

This was pretty easy to do with the old bevy_easings crate, that this crate was based on, because it dealt with the entire Transform all at once.

Here, you are providing separate "lenses" for translation, rotation, and scale, and all of them set the generic parameter on Lens<T> to T: Transform, meaning that I cannot add multiple to the same entity simultaneously. There is no lens for working with the whole transform. :(

So have you made it impossible to tween between two Transforms? Only leaving the possibility to operate on one field at a time?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.