Skip to content

Commit e7cfce3

Browse files
authored
Add Delta Tweens
You can now have multiple tweens affect the same entity without having to make complex calculations or have to pick one of them! In practice, added: Previous value now provided to each interpolator function (you don't have to use it) An example that showcases two tweens affecting the same entity A Delta variant to all basic interpolators
2 parents ff0c61a + 0a3608e commit e7cfce3

16 files changed

Lines changed: 412 additions & 72 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## v0.8.1 - 2025-07-14
4+
5+
### Breaking Changes
6+
7+
- `interpolate` functions now take an additional `previous_value` argument, which you can now use to make delta tweens.
8+
Still, you'd have to update everything that implements `Interpolator` to match the new signature.
9+
10+
### Changes
11+
12+
- Migrate to Bevy 0.16.1
13+
- You can now use `previous_value` to make tweens that apply delta instead of set values
14+
(see `TranslationDelta` for example). This is useful when you want two ongoing tweens to affect the same entity.
15+
316
## v0.8.0 - 2025-05-09
417

518
### Changes

Cargo.toml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ resolver = "2"
1818
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1919

2020
[dependencies.bevy]
21-
version = "0.16.0"
21+
version = "0.16.1"
2222
default-features = false
2323
features = ["std"]
2424

@@ -28,7 +28,7 @@ default-features = false
2828
features = ["curve"]
2929

3030
[dependencies.bevy_time_runner]
31-
version = "0.4.0"
31+
version = "0.4.1"
3232

3333
[dependencies.serde]
3434
version = "1"
@@ -45,7 +45,7 @@ optional = true
4545

4646
[dev-dependencies]
4747
bevy-inspector-egui = "0.31.0"
48-
rand = "0.9.0"
48+
rand = "0.9.1"
4949

5050
[dev-dependencies.bevy]
5151
version = "0.16.0"
@@ -165,3 +165,6 @@ required-features = [
165165
"bevy_lookup_curve",
166166
]
167167

168+
[[example]]
169+
name = "delta_tweens"
170+
path = "examples/demo/delta_tweens.rs"

examples/animation/banner_bounce.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ fn animation(mut commands: Commands, asset_server: Res<AssetServer>) {
362362

363363
type InterpolateSpriteAlpha = Box<dyn Interpolator<Item = Sprite>>;
364364
fn sprite_alpha(start: f32, end: f32) -> InterpolateSpriteAlpha {
365-
Box::new(interpolate::closure(move |sprite: &mut Sprite, value| {
365+
Box::new(interpolate::closure(move |sprite: &mut Sprite, value, _| {
366366
sprite.color = sprite.color.with_alpha(start.lerp(end, value));
367367
}))
368368
}

examples/demo/delta_tweens.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
use std::time::Duration;
2+
use bevy::{
3+
prelude::*,
4+
};
5+
use bevy_tween::{
6+
combinator::*, prelude::*,
7+
tween::AnimationTarget,
8+
};
9+
10+
fn secs(secs: f32) -> Duration {
11+
Duration::from_secs_f32(secs)
12+
}
13+
14+
fn main() {
15+
App::new()
16+
.add_plugins((DefaultPlugins, DefaultTweenPlugins))
17+
.add_systems(Startup, (
18+
setup,
19+
spawn_circle_with_tweens
20+
))
21+
.run();
22+
}
23+
24+
fn setup(mut commands: Commands) {
25+
commands.spawn(Camera2d);
26+
}
27+
28+
fn spawn_circle_with_tweens(
29+
mut commands: Commands,
30+
asset_server: Res<AssetServer>,
31+
) {
32+
let circle_filled_image = asset_server.load("circle_filled.png");
33+
let circle_transform = Transform::from_xyz(400., -200., 0.);
34+
let vertical_delta = Vec3::new(0.0, 400.0, 0.0);
35+
let horizontal_delta = Vec3::new(-800.0, 0.0, 0.0);
36+
let float_duration = secs(4.0);
37+
let circle = AnimationTarget.into_target();
38+
let mut circle_transform_state = circle.transform_state(circle_transform);
39+
40+
let mut circle_commands = commands
41+
.spawn((
42+
Sprite {
43+
image: circle_filled_image,
44+
..default()
45+
},
46+
circle_transform,
47+
AnimationTarget,
48+
));
49+
50+
circle_commands.animation()
51+
.repeat(Repeat::Infinitely)
52+
.repeat_style(RepeatStyle::PingPong)
53+
.insert(parallel((
54+
tween(
55+
float_duration,
56+
EaseKind::BounceIn,
57+
circle_transform_state.translation_delta_by(horizontal_delta),
58+
),
59+
tween(
60+
float_duration,
61+
EaseKind::SineInOut,
62+
circle_transform_state.translation_delta_by(vertical_delta),
63+
)
64+
)));
65+
}

examples/demo/follow.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
9696
.insert_tween_here(
9797
Duration::from_secs(2),
9898
EaseKind::CubicInOut,
99-
jeb.with_closure(|transform: &mut Transform, value| {
99+
jeb.with_closure(|transform: &mut Transform, value, _| {
100100
let start = 0.;
101101
let end = TAU;
102102
transform.rotation =

examples/demo/hold.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ mod interpolate {
2727
impl Interpolator for EffectIntensity {
2828
type Item = super::EffectIntensitiy;
2929

30-
fn interpolate(&self, item: &mut Self::Item, value: f32) {
30+
fn interpolate(&self, item: &mut Self::Item, value: f32, _previous_value: f32) {
3131
item.0 = self.start.lerp(self.end, value)
3232
}
3333
}

examples/demo/sprite_sheet.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ mod interpolate {
2525
impl Interpolator for AtlasIndex {
2626
type Item = Sprite;
2727

28-
fn interpolate(&self, item: &mut Self::Item, value: f32) {
28+
fn interpolate(&self, item: &mut Self::Item, value: f32, _previous_value: f32) {
2929
let Some(texture_atlas) = &mut item.texture_atlas else {
3030
return;
3131
};

examples/interpolator.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ mod interpolate {
3434
impl Interpolator for CircleRadius {
3535
type Item = Circle;
3636

37-
fn interpolate(&self, item: &mut Self::Item, value: f32) {
37+
fn interpolate(&self, item: &mut Self::Item, value: f32, _previous_value: f32) {
3838
item.radius = self.start.lerp(self.end, value);
3939
}
4040
}
@@ -51,7 +51,7 @@ mod interpolate {
5151
impl Interpolator for CircleHue {
5252
type Item = Circle;
5353

54-
fn interpolate(&self, item: &mut Self::Item, value: f32) {
54+
fn interpolate(&self, item: &mut Self::Item, value: f32, _previous_value: f32) {
5555
item.hue = self.start.lerp(self.end, value);
5656
}
5757
}
@@ -99,7 +99,7 @@ fn setup(mut commands: Commands) {
9999
Duration::from_secs(2),
100100
EaseKind::Linear,
101101
// Requires [`component_dyn_tween_system`]
102-
circle.with_closure(|circle: &mut Circle, value| {
102+
circle.with_closure(|circle: &mut Circle, value, _| {
103103
circle.spikiness = (2.).lerp(4., value);
104104
}),
105105
),

src/combinator/state.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,4 +170,19 @@ impl TransformTargetState {
170170
pub fn scale_by(&mut self, by: Vec3) -> ComponentTween<Scale> {
171171
self.scale_with(scale_by(by))
172172
}
173+
174+
/// Create delta [`ComponentTween`] of transform's translation tweening by provided input
175+
pub fn translation_delta_by(&mut self, by: Vec3) -> ComponentTween<TranslationDelta> {
176+
self.translation_with(translation_delta_by(by))
177+
}
178+
179+
/// Create delta [`ComponentTween`] of transform's rotation tweening by provided input
180+
pub fn rotation_delta_by(&mut self, by: Quat) -> ComponentTween<RotationDelta> {
181+
self.rotation_with(rotation_delta_by(by))
182+
}
183+
184+
/// Create delta [`ComponentTween`] of scale's rotation tweening by provided input
185+
pub fn scale_delta_by(&mut self, by: Vec3) -> ComponentTween<ScaleDelta> {
186+
self.scale_with(scale_delta_by(by))
187+
}
173188
}

src/interpolate.rs

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//! - [`AngleZ`]
1212
//! - [`SpriteColor`]
1313
//! - [`ColorMaterial`]
14+
//! - All their delta variants (such as [`TranslationDelta`])
1415
//!
1516
//! # Your own [`Interpolator`]
1617
//!
@@ -45,7 +46,7 @@
4546
//! type Item = Foo;
4647
//!
4748
//! // Then we define how we want to interpolate `Foo`
48-
//! fn interpolate(&self, item: &mut Self::Item, value: f32) {
49+
//! fn interpolate(&self, item: &mut Self::Item, value: f32, _previous_value: f32) {
4950
//! // Usually if the type already have the `.lerp` function provided
5051
//! // by the `FloatExt` trait then we can use just that
5152
//! item.0 = self.start.lerp(self.end, value);
@@ -90,13 +91,13 @@ use bevy::prelude::*;
9091
/// Alias for an `Interpolator` as a boxed trait object.
9192
pub type BoxedInterpolator<Item> = Box<dyn Interpolator<Item = Item>>;
9293

93-
type InterpolatorClosure<I> = Box<dyn Fn(&mut I, f32) + Send + Sync + 'static>;
94+
type InterpolatorClosure<I> = Box<dyn Fn(&mut I, f32, f32) + Send + Sync + 'static>;
9495

9596
/// Create boxed closure in order to be used with dynamic [`Interpolator`]
9697
pub fn closure<I, F>(f: F) -> InterpolatorClosure<I>
9798
where
9899
I: 'static,
99-
F: Fn(&mut I, f32) + Send + Sync + 'static,
100+
F: Fn(&mut I, f32, f32) + Send + Sync + 'static,
100101
{
101102
Box::new(f)
102103
}
@@ -115,7 +116,7 @@ pub trait Interpolator: Send + Sync + 'static {
115116
/// The value should be already sampled from an [`Interpolation`]
116117
///
117118
/// [`Interpolation`]: crate::interpolation::Interpolation
118-
fn interpolate(&self, item: &mut Self::Item, value: f32);
119+
fn interpolate(&self, item: &mut Self::Item, value: f32, previous_value: f32);
119120
}
120121

121122
// /// Reflect [`Interpolator`] trait
@@ -197,7 +198,7 @@ pub trait Interpolator: Send + Sync + 'static {
197198

198199
/// Default interpolators
199200
///
200-
/// Register type and systems for the following interpolators:
201+
/// Register type and systems for the following interpolators and their delta interpolators:
201202
/// - [`Translation`]
202203
/// - [`Rotation`]
203204
/// - [`Scale`]
@@ -217,29 +218,47 @@ impl Plugin for DefaultInterpolatorsPlugin {
217218
tween::component_tween_system::<Rotation>(),
218219
tween::component_tween_system::<Scale>(),
219220
tween::component_tween_system::<AngleZ>(),
221+
tween::component_tween_system::<TranslationDelta>(),
222+
tween::component_tween_system::<RotationDelta>(),
223+
tween::component_tween_system::<ScaleDelta>(),
224+
tween::component_tween_system::<AngleZDelta>()
220225
))
221226
.register_type::<tween::ComponentTween<Translation>>()
222227
.register_type::<tween::ComponentTween<Rotation>>()
223228
.register_type::<tween::ComponentTween<Scale>>()
224-
.register_type::<tween::ComponentTween<AngleZ>>();
229+
.register_type::<tween::ComponentTween<AngleZ>>()
230+
.register_type::<tween::ComponentTween<TranslationDelta>>()
231+
.register_type::<tween::ComponentTween<RotationDelta>>()
232+
.register_type::<tween::ComponentTween<ScaleDelta>>()
233+
.register_type::<tween::ComponentTween<AngleZDelta>>();
225234

226235
#[cfg(feature = "bevy_sprite")]
227-
app.add_tween_systems(tween::component_tween_system::<SpriteColor>())
228-
.register_type::<tween::ComponentTween<SpriteColor>>();
236+
app.add_tween_systems((
237+
tween::component_tween_system::<SpriteColor>(),
238+
tween::component_tween_system::<SpriteColorDelta>(),
239+
))
240+
.register_type::<tween::ComponentTween<SpriteColor>>()
241+
.register_type::<tween::ComponentTween<SpriteColorDelta>>();
229242

230243
#[cfg(feature = "bevy_ui")]
231244
app.add_tween_systems((
232245
tween::component_tween_system::<ui::BackgroundColor>(),
233246
tween::component_tween_system::<ui::BorderColor>(),
247+
tween::component_tween_system::<BackgroundColorDelta>(),
248+
tween::component_tween_system::<BorderColorDelta>(),
234249
))
235-
.register_type::<tween::ComponentTween<ui::BackgroundColor>>()
236-
.register_type::<tween::ComponentTween<ui::BorderColor>>();
250+
.register_type::<tween::ComponentTween<ui::BackgroundColor>>()
251+
.register_type::<tween::ComponentTween<ui::BorderColor>>()
252+
.register_type::<tween::ComponentTween<BackgroundColorDelta>>()
253+
.register_type::<tween::ComponentTween<BorderColorDelta>>();
237254

238255
#[cfg(all(feature = "bevy_sprite", feature = "bevy_asset",))]
239-
app.add_tween_systems(
256+
app.add_tween_systems((
240257
tween::asset_tween_system::<sprite::ColorMaterial>(),
241-
)
242-
.register_type::<tween::AssetTween<sprite::ColorMaterial>>();
258+
tween::asset_tween_system::<ColorMaterialDelta>(),
259+
))
260+
.register_type::<tween::AssetTween<sprite::ColorMaterial>>()
261+
.register_type::<tween::ComponentTween<ColorMaterialDelta>>();
243262
}
244263
}
245264

0 commit comments

Comments
 (0)