Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6ba34fb
allow updating lens on tween start
meepleek Apr 6, 2022
b5e1264
Merge branch 'djeedai:main' into main
meepleek Apr 18, 2022
0c5253c
Merge branch 'main' of https://github.com/djeedai/bevy_tweening into …
meepleek Sep 18, 2022
2c94cc4
fix merge
meepleek Sep 18, 2022
68ac8ad
potential fix for the duration sub overflow
meepleek Sep 20, 2022
fea89f4
Merge branch 'djeedai:main' into bevy_0_10
meepleek Mar 16, 2023
19aa443
fix update_on_tween_start
meepleek Mar 16, 2023
be9313a
Merge pull request #1 from SecretPocketCat/bevy_0_10
meepleek Mar 16, 2023
f607e6a
use elapsed & add direction + times_completed to callback
meepleek Jun 7, 2023
6bb8747
fix: change: times_completed to i32
meepleek Jun 7, 2023
31db2db
add settings with default event_data
meepleek Jun 7, 2023
0e5f387
Merge branch 'main' of https://github.com/djeedai/bevy_tweening into …
meepleek Jul 27, 2023
c335a67
Merge pull request #3 from SecretPocketCat/djeedai-main
meepleek Jul 27, 2023
05f964a
Merge branch 'djeedai:main' into main
meepleek Dec 5, 2023
fa7302a
Merge remote-tracking branch 'upstream/main' into bevy_14
Jul 25, 2024
3ca18a1
reexport Ease trait
Jul 28, 2024
5f0edc7
Merge pull request #5 from SecretPocketCat/bevy_14
meepleek Jul 28, 2024
dc4ded1
merge: commit 'b0bae2ae67a927828ff8a2faa463db0963e1a942' into bevy-0-15
jhanc-helix Apr 27, 2025
bc72d53
Merge pull request #6 from meepleek/bevy-0-15
jhanc-helix Apr 27, 2025
836a552
revert: TweenSettings
jhanc-helix Apr 27, 2025
40b5844
Merge pull request #7 from meepleek/revert_tween_settings
jhanc-helix Apr 27, 2025
af9c2cd
Merge remote-tracking branch 'upstream/main' into bevy_0_16
May 1, 2025
2e0742b
Merge pull request #8 from meepleek/bevy_0_16
meepleek May 1, 2025
a5de674
feat: add marker generic param
meepleek Jun 24, 2025
d33e013
feat: add marker to component_animator_system
meepleek Jun 24, 2025
2bb300b
docs: add marker example
meepleek Jun 24, 2025
b141fdc
refactor: remove marker from tweenables and mark only the animators
meepleek Jun 24, 2025
2c097c7
revert: cargo.toml formatting
meepleek Jun 24, 2025
ff3c876
refactor: replace new_with_marker by with_marker
meepleek Jun 24, 2025
d4e9654
Merge branch 'main' into animator_marker
meepleek Aug 1, 2025
ea23e49
refactor: align AssetAnimator marker with Animator
meepleek Aug 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ required-features = ["bevy_ui", "bevy_text", "bevy/bevy_winit", "bevy/bevy_picki
name = "sequence"
required-features = ["bevy_sprite", "bevy_text", "bevy/bevy_winit", "bevy/bevy_picking"]

[[example]]
name = "transform_marker"
required-features = ["bevy/bevy_winit"]

[[example]]
name = "custom_relative_lens"
required-features = ["bevy/bevy_winit"]

[workspace]
resolver = "2"
members = [".", "benchmarks/"]
65 changes: 65 additions & 0 deletions examples/custom_relative_lens.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use bevy::prelude::*;
use bevy_tweening::{lens::*, *};

fn main() -> Result<(), Box<dyn std::error::Error>> {
App::default()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "CustomRelativeLens".to_string(),
resolution: (1200., 600.).into(),
present_mode: bevy::window::PresentMode::Fifo, // vsync
..default()
}),
..default()
}))
.add_system(bevy::window::close_on_esc)
.add_plugin(TweeningPlugin)
.add_startup_system(setup)
.run();

Ok(())
}

fn setup(mut commands: Commands) {
commands.spawn(Camera2dBundle::default());

let size = 25.;
let screen_y = 150.;

let tween = Tween::new(
EaseFunction::QuadraticInOut,
std::time::Duration::from_millis(500),
TransformRelativePositionLens {
end: Vec3::new(100., -screen_y, 0.),
..Default::default()
},
);

commands
.spawn(SpriteBundle {
sprite: Sprite {
color: Color::RED,
custom_size: Some(Vec2::new(size, size)),
..Default::default()
},
..Default::default()
})
.insert(Animator::new(tween));
}

#[derive(Default)]
pub struct TransformRelativePositionLens {
start: Vec3,
pub end: Vec3,
}

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

fn update_on_tween_start(&mut self, target: &Transform) {
self.start = target.translation;
}
}
117 changes: 117 additions & 0 deletions examples/transform_marker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use bevy::{color::palettes::css::*, prelude::*};
use bevy_inspector_egui::{bevy_egui::EguiPlugin, prelude::*, quick::ResourceInspectorPlugin};

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

mod utils;

fn main() {
App::default()
.add_plugins((
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "TransformPositionLens".to_string(),
resolution: (1400., 600.).into(),
present_mode: bevy::window::PresentMode::Fifo, // vsync
..default()
}),
..default()
}),
EguiPlugin {
enable_multipass_for_primary_context: true,
},
//DefaultInspectorConfigPlugin,
ResourceInspectorPlugin::<Options>::new(),
TweeningPlugin,
))
.init_resource::<Options>()
.register_type::<Options>()
.add_systems(Update, utils::close_on_esc)
.add_systems(Startup, setup)
.add_systems(Update, update_animation_speed)
.add_systems(
Update,
(
component_animator_system::<Transform, TransformTranslation>,
component_animator_system::<Transform, TransformScale>,
component_animator_system::<Transform, TransformRotation>,
),
)
.run();
}

#[derive(Resource, Reflect, InspectorOptions)]
#[reflect(InspectorOptions)]
struct Options {
#[inspector(min = 0.01, max = 100.)]
speed: f32,
}

impl Default for Options {
fn default() -> Self {
Self { speed: 1. }
}
}

struct TransformTranslation;
struct TransformScale;
struct TransformRotation;

fn setup(mut commands: Commands) {
commands.spawn(Camera2d::default());

let size = 25.;
let screen_y = 150.;

let translation_tween = Tween::new(
EaseFunction::QuadraticInOut,
std::time::Duration::from_secs(1),
TransformPositionLens {
start: Vec3::new(0., screen_y, 0.),
end: Vec3::new(0., -screen_y, 0.),
},
)
.with_repeat_count(RepeatCount::Infinite)
.with_repeat_strategy(RepeatStrategy::MirroredRepeat);
let scale_tween = Tween::new(
EaseFunction::SineInOut,
std::time::Duration::from_secs_f32(0.5),
TransformScaleLens {
start: Vec3::ONE,
end: Vec2::splat(1.5).extend(1.),
},
)
.with_repeat_count(RepeatCount::Infinite)
.with_repeat_strategy(RepeatStrategy::MirroredRepeat);
let rotation_tween = Tween::new(
EaseFunction::QuarticInOut,
std::time::Duration::from_secs_f32(0.75),
TransformRotationLens {
start: Quat::IDENTITY,
end: Quat::from_axis_angle(Vec3::Z, std::f32::consts::PI / 2.),
},
)
.with_repeat_count(RepeatCount::Infinite)
.with_repeat_strategy(RepeatStrategy::MirroredRepeat);

commands.spawn((
Sprite {
color: RED.into(),
custom_size: Some(Vec2::splat(size)),
..default()
},
Animator::new(translation_tween).with_marker::<TransformTranslation>(),
Animator::new(scale_tween).with_marker::<TransformScale>(),
Animator::new(rotation_tween).with_marker::<TransformRotation>(),
));
}

fn update_animation_speed(options: Res<Options>, mut animators: Query<&mut Animator<Transform>>) {
if !options.is_changed() {
return;
}

for mut animator in animators.iter_mut() {
animator.set_speed(options.speed);
}
}
13 changes: 13 additions & 0 deletions src/lens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ use bevy::prelude::*;

use crate::Targetable;

use crate::TweeningDirection;

/// A lens over a subset of a component.
///
/// The lens takes a `target` component or asset from a query, as a mutable
Expand Down Expand Up @@ -74,6 +76,17 @@ pub trait Lens<T> {
/// implementation decides which fields are interpolated, and performs
/// the animation in-place, overwriting the target.
fn lerp(&mut self, target: &mut dyn Targetable<T>, ratio: f32);

/// Update lens on tween start
/// Can be used for relative lenses
#[allow(unused_variables)]
fn update_on_tween_start(
&mut self,
target: &mut dyn Targetable<T>,
direction: TweeningDirection,
times_completed: i32,
) {
}
}

/// A lens to manipulate the [`color`] field of a section of a [`Text`]
Expand Down
38 changes: 34 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@
//! [`TextColor`]: https://docs.rs/bevy/0.16.0/bevy/text/struct.TextColor.html
//! [`Transform`]: https://docs.rs/bevy/0.16.0/bevy/transform/components/struct.Transform.html

use std::time::Duration;
use std::{marker::PhantomData, time::Duration};

use bevy::prelude::*;

Expand Down Expand Up @@ -503,16 +503,17 @@ macro_rules! animator_impl {
/// entity as the [`Animator<T>`] itself. But if [`Animator::target`] is set,
/// that entity will be used instead.
#[derive(Component)]
pub struct Animator<T: Component> {
pub struct Animator<T: Component, M = ()> {
/// Control if this animation is played or not.
pub state: AnimatorState,
/// When set, the animated component will be the one located on this entity.
pub target: Option<Entity>,
tweenable: BoxedTweenable<T>,
speed: f32,
_marker: PhantomData<M>,
}

impl<T: Component + std::fmt::Debug> std::fmt::Debug for Animator<T> {
impl<T: Component + std::fmt::Debug, M> std::fmt::Debug for Animator<T, M> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Animator")
.field("state", &self.state)
Expand All @@ -529,9 +530,23 @@ impl<T: Component> Animator<T> {
tweenable: Box::new(tween),
target: None,
speed: 1.,
_marker: Default::default(),
}
}

/// Create a new version of this animator component with a marker
pub fn with_marker<M>(self) -> Animator<T, M> {
Animator::<T, M> {
state: self.state,
tweenable: self.tweenable,
target: self.target,
speed: self.speed,
_marker: Default::default(),
}
}
}

impl<T: Component, M> Animator<T, M> {
/// Create a new version of this animator with the `target` set to the given entity.
pub fn with_target(mut self, entity: Entity) -> Self {
self.target = Some(entity);
Expand All @@ -547,11 +562,12 @@ impl<T: Component> Animator<T> {
/// located on the same entity as the [`AssetAnimator<T>`] itself.
#[cfg(feature = "bevy_asset")]
#[derive(Component)]
pub struct AssetAnimator<T: Asset> {
pub struct AssetAnimator<T: Asset, M = ()> {
/// Control if this animation is played or not.
pub state: AnimatorState,
tweenable: BoxedTweenable<T>,
speed: f32,
_marker: PhantomData<M>,
}

#[cfg(feature = "bevy_asset")]
Expand All @@ -572,9 +588,23 @@ impl<T: Asset> AssetAnimator<T> {
state: default(),
tweenable: Box::new(tween),
speed: 1.,
_marker: Default::default(),
}
}

/// Create a new version of this asset animator component with a marker
pub fn with_marker<M>(self) -> AssetAnimator<T, M> {
AssetAnimator::<T, M> {
state: self.state,
tweenable: self.tweenable,
speed: self.speed,
_marker: Default::default(),
}
}
}

#[cfg(feature = "bevy_asset")]
impl<T: Asset, M> AssetAnimator<T, M> {
animator_impl!();
}

Expand Down
21 changes: 11 additions & 10 deletions src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,25 @@ impl Plugin for TweeningPlugin {
fn build(&self, app: &mut App) {
app.add_event::<TweenCompleted>().add_systems(
Update,
component_animator_system::<Transform>.in_set(AnimationSystem::AnimationUpdate),
component_animator_system::<Transform, ()>.in_set(AnimationSystem::AnimationUpdate),
);

#[cfg(feature = "bevy_ui")]
app.add_systems(
Update,
component_animator_system::<Node>.in_set(AnimationSystem::AnimationUpdate),
component_animator_system::<Node, ()>.in_set(AnimationSystem::AnimationUpdate),
);
#[cfg(feature = "bevy_ui")]
app.add_systems(
Update,
component_animator_system::<BackgroundColor>.in_set(AnimationSystem::AnimationUpdate),
component_animator_system::<BackgroundColor, ()>
.in_set(AnimationSystem::AnimationUpdate),
);

#[cfg(feature = "bevy_sprite")]
app.add_systems(
Update,
component_animator_system::<Sprite>.in_set(AnimationSystem::AnimationUpdate),
component_animator_system::<Sprite, ()>.in_set(AnimationSystem::AnimationUpdate),
);

#[cfg(all(feature = "bevy_sprite", feature = "bevy_asset"))]
Expand All @@ -69,7 +70,7 @@ impl Plugin for TweeningPlugin {
#[cfg(feature = "bevy_text")]
app.add_systems(
Update,
component_animator_system::<TextColor>.in_set(AnimationSystem::AnimationUpdate),
component_animator_system::<TextColor, ()>.in_set(AnimationSystem::AnimationUpdate),
);
}
}
Expand All @@ -85,9 +86,9 @@ pub enum AnimationSystem {
///
/// This system extracts all components of type `T` with an [`Animator<T>`]
/// attached to the same entity, and tick the animator to animate the component.
pub fn component_animator_system<T: Component<Mutability = Mutable>>(
pub fn component_animator_system<T: Component<Mutability = Mutable>, M: Send + Sync + 'static>(
time: Res<Time>,
mut animator_query: Query<(Entity, &mut Animator<T>)>,
mut animator_query: Query<(Entity, &mut Animator<T, M>)>,
mut target_query: Query<&mut T>,
events: ResMut<Events<TweenCompleted>>,
mut commands: Commands,
Expand Down Expand Up @@ -273,7 +274,7 @@ mod tests {
)
.with_completed_event(0);
let mut env = TestEnv::new_separated(Animator::new(tween));
let mut system = IntoSystem::into_system(component_animator_system::<Transform>);
let mut system = IntoSystem::into_system(component_animator_system::<Transform, ()>);
system.initialize(env.world_mut());

env.tick(Duration::ZERO, &mut system);
Expand Down Expand Up @@ -305,7 +306,7 @@ mod tests {

// fn nit() {}
// let mut system = IntoSystem::into_system(nit);
let mut system = IntoSystem::into_system(component_animator_system::<Transform>);
let mut system = IntoSystem::into_system(component_animator_system::<Transform, ()>);
system.initialize(env.world_mut());

env.tick(Duration::ZERO, &mut system);
Expand Down Expand Up @@ -385,7 +386,7 @@ mod tests {
let component = env.component_mut();
assert!(component.is_changed());

let mut system = IntoSystem::into_system(component_animator_system::<DummyComponent>);
let mut system = IntoSystem::into_system(component_animator_system::<DummyComponent, ()>);
system.initialize(env.world_mut());

assert!(!defer.load(Ordering::SeqCst));
Expand Down
Loading
Loading