Expand description
§Getting started
DefaultTweenPlugins
provide most the stuff you will need.
Add the plugin to your app:
use bevy::prelude::*;
use bevy_tween::prelude::*;
fn main() {
App::default()
.add_plugins((DefaultPlugins, DefaultTweenPlugins))
.run();
}
§Usages
§Creating animation
Let say we have this sprite:
let mut sprite_commands = commands.spawn(SpriteBundle {
sprite: Sprite {
custom_size: Some(Vec2::new(50., 50.)),
..Default::default()
},
..Default::default()
});
Now we want to animate it, maybe with one simple tween animation.
§insert_tween_here
method
We can do this:
use bevy_tween::{interpolate::sprite_color, combinator::tween};
let sprite = sprite_commands.id().into_target();
sprite_commands.animation().insert_tween_here(
Duration::from_secs(1),
EaseKind::QuadraticOut,
sprite.with(sprite_color(WHITE.into(), RED.into()))
// Since this argument accepts a bundle, you can add additional tween to this like so:
// (
// sprite.with(sprite_color(WHITE.into(), RED.into())),
// sprite.with(something_something(...)),
// )
);
This insert every animation components directly into your entity. Use this if you wish to keep your entity structure simple (no children) and doesn’t need complicated animation.
§Combinator framework
Now what if we want to chain animations?
This crate provide a way to create animation using combinator framework. So we can just:
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()))
),
)));
This adds one TimeRunner
to your entity and 2 Tween
entities as the child.
§State
There’s a little bit of boilerplate we can improve.
Currently we’ve specified RED.into()
2 times because we want our tween to
continue from previous value.
We can use state for this:
use bevy_tween::{
interpolate::sprite_color_to,
combinator::{tween, sequence}
};
let sprite = sprite_commands.id().into_target();
let mut sprite_color = sprite.state(WHITE.into()); // We want the intial color to be white
sprite_commands.animation().insert(sequence((
tween(
Duration::from_secs(1),
EaseKind::QuadraticOut,
// Switch the constructor to the relative variant
sprite_color.with(sprite_color_to(RED.into()))
),
tween(
Duration::from_secs(1),
EaseKind::QuadraticIn,
sprite_color.with(sprite_color_to(WHITE.into()))
),
)));
Looks good!
§Repeating
If we want to repeat our animation to so we can do:
use bevy_tween::{
interpolate::sprite_color_to,
combinator::{tween, sequence}
};
let sprite = sprite_commands.id().into_target();
let mut sprite_color = sprite.state(WHITE.into());
sprite_commands.animation()
.repeat(Repeat::Infinitely) // Add repeat
.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()))
),
)));
§Custom combinator
What if you want to abstract animation?
- To manage large animation code
- To reuse animation code
- Custom combinators
Combinator framework got you covered!:
use bevy_tween::{
interpolate::sprite_color_to,
combinator::{AnimationCommands, TargetState, tween, sequence},
tween::TargetComponent,
};
// Create new combinator
fn my_animation(
// You can use `TargetComponent` if you doesn't use state.
target_sprite_color: &mut TargetState<TargetComponent, Color>,
duration: Duration
) -> impl FnOnce(&mut AnimationCommands, &mut Duration) {
sequence((
tween(
duration,
EaseKind::QuadraticOut,
target_sprite_color.with(sprite_color_to(RED.into()))
),
tween(
duration,
EaseKind::QuadraticIn,
target_sprite_color.with(sprite_color_to(WHITE.into()))
),
))
}
let sprite = sprite_commands.id().into_target();
let mut sprite_color = sprite.state(WHITE.into());
sprite_commands.animation()
.repeat(Repeat::Infinitely)
.insert(my_animation(&mut sprite_color, Duration::from_secs(1)));
There are more combinators you can use. Check them out in the combinator
module.
§Animator as a child
You can spawn animator as a child if you want
use bevy_tween::{
interpolate::sprite_color,
combinator::{tween, sequence}
};
let sprite = sprite_commands.id().into_target();
sprite_commands.with_children(|c| {
c.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()))
),
)));
});
§Orphaned Animator
An animator does not required to be a child or inside your target entity. You can spawn them anywhere in the world if needed.
use bevy_tween::{
interpolate::sprite_color,
combinator::{tween, sequence}
};
let sprite = sprite_commands.id().into_target();
// use `.animation()` on commands directly to spawn a new entity
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()))
),
)));
§AnimationTarget
AnimationTarget
can be used for automatic target searching.
use bevy_tween::{
interpolate::sprite_color,
combinator::tween,
tween::AnimationTarget,
};
let sprite = AnimationTarget.into_target(); // which returns TargetComponent::Marker
sprite_commands.insert(AnimationTarget);
sprite_commands.animation().insert_tween_here(
Duration::from_secs(1),
EaseKind::QuadraticOut,
sprite.with(sprite_color(WHITE.into(), RED.into()))
);
§Custom interpolator
See these documentations for more details:
This example shows how to create your own inteprolator.
interpolator
example
interpolator
exampleuse bevy::{prelude::*, time::Stopwatch};
use bevy_tween::{
combinator::{parallel, tween},
prelude::*,
};
#[derive(Component)]
pub struct Circle {
radius: f32,
hue: f32,
spikiness: f32,
}
mod interpolate {
use super::Circle;
use bevy::prelude::*;
use bevy_tween::{
component_dyn_tween_system, component_tween_system, prelude::*,
};
pub fn interpolators_plugin(app: &mut App) {
app.add_tween_systems((
component_dyn_tween_system::<Circle>(),
component_tween_system::<CircleRadius>(),
component_tween_system::<CircleHue>(),
));
}
pub struct CircleRadius {
start: f32,
end: f32,
}
impl Interpolator for CircleRadius {
type Item = Circle;
fn interpolate(&self, item: &mut Self::Item, value: f32) {
item.radius = self.start.lerp(self.end, value);
}
}
pub fn circle_radius(start: f32, end: f32) -> CircleRadius {
CircleRadius { start, end }
}
pub struct CircleHue {
start: f32,
end: f32,
}
impl Interpolator for CircleHue {
type Item = Circle;
fn interpolate(&self, item: &mut Self::Item, value: f32) {
item.hue = self.start.lerp(self.end, value);
}
}
pub fn circle_hue(start: f32, end: f32) -> CircleHue {
CircleHue { start, end }
}
}
use interpolate::{circle_hue, circle_radius};
fn main() {
App::new()
.add_plugins((
MinimalPlugins,
DefaultTweenPlugins,
interpolate::interpolators_plugin,
))
.add_systems(Startup, setup)
.add_systems(Update, what_happen)
.run();
}
fn setup(mut commands: Commands) {
let circle_id = commands
.spawn(Circle {
radius: 1.,
hue: 0.,
spikiness: 2.,
})
.id();
let circle = circle_id.into_target();
commands.animation().insert(parallel((
tween(
Duration::from_secs(2),
EaseKind::Linear,
circle.with(circle_hue(0., 10.)),
),
tween(
Duration::from_secs(2),
EaseKind::Linear,
circle.with(circle_radius(1., 50.)),
),
tween(
Duration::from_secs(2),
EaseKind::Linear,
// Requires [`component_dyn_tween_system`]
circle.with_closure(|circle: &mut Circle, value| {
circle.spikiness = (2.).lerp(4., value);
}),
),
)));
}
fn what_happen(
time: Res<Time>,
q_circle: Query<&Circle>,
mut time_passed: Local<Stopwatch>,
mut print_tick: Local<Stopwatch>,
) {
time_passed.tick(time.delta());
print_tick.tick(time.delta());
if time_passed.elapsed_secs() < 3. && print_tick.elapsed_secs() > 0.2 {
let circle = q_circle.single();
println!(
"{:.2} {:.2} {:.2}",
circle.hue, circle.radius, circle.spikiness
);
print_tick.reset();
}
}
§Entity structure
This example shows what’s actually going on under the hood within this crate’s API.
entity_structure
example
entity_structure
exampleRun cargo run --example entity_structure
to see this in action.
use bevy::prelude::*;
use bevy_tween::interpolate::{AngleZ, Translation};
use bevy_tween::prelude::*;
use bevy_tween::{
bevy_time_runner::{TimeRunner, TimeSpan},
tween::{AnimationTarget, TargetComponent},
};
fn main() {
App::new()
.add_plugins((DefaultPlugins, DefaultTweenPlugins))
.add_systems(Startup, setup)
.run();
}
fn sprite(start_x: f32, start_y: f32) -> (Sprite, Transform) {
(
Sprite {
custom_size: Some(Vec2::new(50., 50.)),
color: Color::WHITE,
..default()
},
Transform::from_xyz(start_x, start_y, 0.),
)
}
/// This show all the possible structure you can use.
/// All of these result in exactly the same animation!
/// Just use what fit for your use case.
///
/// These will be presented in its most rawest form.
/// See other examples for better APIs.
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
let angle_start = 0.;
let angle_end = std::f32::consts::PI * 2.;
let start_x = -300.;
let end_x = 300.;
let spacing_y = 100.;
let offset_y = -(spacing_y * 3.) / 2.;
// Everything in the same entity
let y = 0. * spacing_y + offset_y;
commands.spawn((
sprite(start_x, y),
AnimationTarget,
TimeRunner::new(Duration::from_secs(5)),
TimeSpan::try_from(..Duration::from_secs(5)).unwrap(),
EaseKind::QuadraticInOut,
ComponentTween::new_target(
TargetComponent::marker(),
Translation {
start: Vec3::new(start_x, y, 0.),
end: Vec3::new(end_x, y, 0.),
},
),
ComponentTween::new_target(
TargetComponent::marker(),
AngleZ {
start: angle_start,
end: angle_end,
},
),
));
// equivalent to
//
// let target = TargetComponent::marker();
// commands.spawn((sprite(...), AnimationTarget)).animate()
// .insert_here(
// Duration::from_secs(5),
// EaseKind::QuadraticOut,
// (
// target.with(translation(...)),
// target.with(angle_z(...)),
// ),
// );
// Sprite and time runner as parent, tweens as children.
let y = 1. * spacing_y + offset_y;
commands
.spawn((
sprite(start_x, y),
AnimationTarget,
TimeRunner::new(Duration::from_secs(5)),
))
.with_children(|c| {
c.spawn((
TimeSpan::try_from(..Duration::from_secs(5)).unwrap(),
EaseKind::QuadraticInOut,
ComponentTween::new_target(
TargetComponent::marker(),
Translation {
start: Vec3::new(start_x, y, 0.),
end: Vec3::new(end_x, y, 0.),
},
),
ComponentTween::new_target(
TargetComponent::marker(),
AngleZ {
start: angle_start,
end: angle_end,
},
),
));
});
// equivalent to
//
// let target = TargetComponent::marker();
// commands.spawn((sprite(...), AnimationTarget))
// .animate()
// .insert(tween(
// Duration::from_secs(5),
// EaseKind::QuadraticOut,
// (
// target.with(translation(...)),
// target.with(angle_z(...)),
// ),
// ));
// Only Sprite as parent, time runner and tweens as children.
let y = 2. * spacing_y + offset_y;
commands
.spawn((sprite(start_x, y), AnimationTarget))
.with_children(|c| {
c.spawn((
TimeRunner::new(Duration::from_secs(5)),
TimeSpan::try_from(..Duration::from_secs(5)).unwrap(),
EaseKind::QuadraticInOut,
ComponentTween::new_target(
TargetComponent::marker(),
Translation {
start: Vec3::new(start_x, y, 0.),
end: Vec3::new(end_x, y, 0.),
},
),
ComponentTween::new_target(
TargetComponent::marker(),
AngleZ {
start: angle_start,
end: angle_end,
},
),
));
});
// equivalent to
//
// let target = TargetComponent::marker();
// commands.spawn((sprite(...), AnimationTarget)).with_children(|c| {
// c.animate().insert_here(
// Duration::from_secs(5),
// EaseKind::QuadraticOut,
// (
// target.with(translation(...)),
// target.with(angle_z(...))
// ),
// );
// });
// Only Sprite as parent, tweens as children of a time runner.
let y = 3. * spacing_y + offset_y;
commands
.spawn((sprite(start_x, y), AnimationTarget))
.with_children(|c| {
c.spawn(TimeRunner::new(Duration::from_secs(5)))
.with_children(|c| {
c.spawn((
TimeSpan::try_from(..Duration::from_secs(5)).unwrap(),
EaseKind::QuadraticInOut,
ComponentTween::new_target(
TargetComponent::marker(),
Translation {
start: Vec3::new(start_x, y, 0.),
end: Vec3::new(end_x, y, 0.),
},
),
ComponentTween::new_target(
TargetComponent::marker(),
AngleZ {
start: angle_start,
end: angle_end,
},
),
));
});
});
// equivalent to
//
// let target = TargetComponent::marker();
// commands.spawn((sprite(...), AnimationTarget)).with_children(|c| {
// c.animate().insert(tween(
// Duration::from_secs(5),
// EaseKind::QuadraticOut,
// (
// target.with(translation(...)),
// target.with(angle_z(...))
// ),
// ));
// });
// or with this completely detached
let y = 4. * spacing_y + offset_y;
let sprite = commands.spawn(sprite(start_x, y)).id();
commands
.spawn(TimeRunner::new(Duration::from_secs(5)))
.with_children(|c| {
c.spawn((
TimeSpan::try_from(..Duration::from_secs(5)).unwrap(),
EaseKind::QuadraticInOut,
ComponentTween::new_target(
sprite,
Translation {
start: Vec3::new(start_x, y, 0.),
end: Vec3::new(end_x, y, 0.),
},
),
ComponentTween::new_target(
sprite,
AngleZ {
start: angle_start,
end: angle_end,
},
),
));
});
// equivalent to
//
// let target = TargetComponent::entity(sprite);
// commands.animate().insert(tween(
// Duration::from_secs(5),
// EaseKind::QuadraticOut,
// (
// target.with(translation(...)),
// target.with(angle_z(...))
// ),
// ));
}
Re-exports§
pub use tween::asset_dyn_tween_system;
bevy_asset
pub use tween::asset_tween_system;
bevy_asset
pub use tween::component_dyn_tween_system;
bevy_asset
pub use tween::component_tween_system;
pub use tween::resource_dyn_tween_system;
pub use tween::resource_tween_system;
pub use tween_event::tween_event_system;
pub use bevy_lookup_curve;
bevy_lookup_curve
pub use bevy_time_runner;
Modules§
- Combinator framework
- Module containing some basic built-in interpolator
- Module containing ease functions and related systems.
- Commonly used items
- Module containing implementations for tween
- Module containing implementations for simple event
Structs§
- Default plugins for using crate.
- This resource will be used while initializing tween plugin and systems.
BevyTweenRegisterSystems
for example. - Configure
TweenSystemSet
and register types.
Enums§
- Enum of SystemSet in this crate. See
TweenCorePlugin
for default system configuration.
Traits§
- Helper trait to add systems by this crate to your app and avoid mistake from forgetting to use the intended schedule and set.