| Crates.io | weirdboi_tween |
| lib.rs | weirdboi_tween |
| version | 0.1.0 |
| created_at | 2025-05-04 07:06:34.109936+00 |
| updated_at | 2025-05-04 07:06:34.109936+00 |
| description | Relationship based component value tweening for Bevy projects |
| homepage | |
| repository | https://weirdboi.dev/libraries/weirdboi-tween |
| max_upload_size | |
| id | 1659409 |
| size | 135,356 |
A component value tweening library for Bevy. Tweens become first class entities, working via the new relationships system.
Add the following to your Cargo.toml:
[dependencies]
weirdboi_tween = "0.1.0"
Add the tweening plugin to your Bevy app:
use bevy::prelude::*;
use weirdboi_tween::TweenPlugin;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(TweenPlugin)
// Other plugins and systems
.run();
}
Since tweens are relationship based entities, they can take advantage of all of the
relationship machinery to work with them. This means using the Tweens collection
to spawn related entities for a target entity, or by creating a Tween independently
and inserting a TweenTarget component.
fn spawn_animated_sprite(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn((
Sprite::from_image(asset_server.load("sprite.png")),
Tweens::spawn(&[(
TweenSpriteColour::tween(
Duration::from_millis(250),
Color::WHITE.into(),
Color::BLACK.into(),
EaseTween::Linear,
),
TweenTransformTranslation::tween(
Duration::from_millis(250),
Vec3::splat(0.0),
Vec3::new(20.0, 50.0, 0.0),
EaseTween::Linear,
),
)])
));
}
The tween system requires the implementation of Tweenable meta types, so named because they
exist at the type level. A Tweenable is associated with one component type, and one data type.
The data type represents some facet of the component that can have an easing applied to it -
this also means the data type does not need to match the type of the component property being
tweened.
Once you have a type implementing Tweenable, you need to register it with the application
in order to set up the required systems.
struct TileOffset(Vec2);
struct TweenTileOffset;
impl Tweenable for TweenTileOffset {
type Comp = TileOffset;
type Data = Vec2;
fn current_value(cmp: &Self::Comp) -> Self::Data {
cmp.0
}
fn update_component(cmp: &mut Self::Comp, value: Self::Data) {
cmp.0 = value;
}
}
// .. Then register in a plugin
fn TileTweenPlugin(app: &mut App) {
app.register_tweenable::<TweenTileOffset>();
}
// .. Then you can spawn the tween
fn tween_player_offset(mut commands: Commands, marked_entity: Single<Entity, With<Player>>) {
commands.spawn((
TweenTarget(*marked_entity),
TweenTileOffset::tween(
Duration::from_millis(200),
Vec2::splat(0.0),
Vec2::new(50., 50.),
TweenEasing::Linear,
),
))
}
By default a tween will run once, complete, and then despawn. This behaviour can be controlled by including
a TweenMode component alongside a tween. TweenMode has three variants:
Once: Default behaviour, tween runs from start to end and stopsLoop: The tween runs from start to end, and then resets to continue running from start to end indefinitely. E.g. 1, 2, 3, loop, 1, 2, 3, loop, 1, 2, 3PingPong: The tween runs from start to end, and the flips values to run from end to start indefinitely. E.g. 1, 2, 3, loop, 3, 2, 1, loop, 1, 2, 3Any tween can have an arbitrary u32 value associated with it, which will cause an event to be fired
in one of two situations, depending on the mode.
When attaching user data to a one shot tween (TweenMode::Once, the default), a TweenComplete event is
fired once the tween completes. This can either be responded to with another system taking an
EventReader<TweenComplete> parameter, or by directly attaching an observer to the target entity that
takes a Trigger<TweenComplete>
When attaching user data to a looping tween (TweenMode::PingPong or TweenMode::Loop), a TweenLooped event
is fired each time the tween iterates. Much like the TweenComplete event, either an EventReader<TweenLooped>
system or Trigger<TweenLooped> observer can be used to respond to this action.
fn handle_tween_complete(mut events: EventReader<TweenComplete>) {
for event in events.read() {
println!("Tween completed for entity: {:?}", event.entity);
if event.user_data == MY_CUSTOM_EVENT_ID {
// Handle specific tween completion
}
}
}
There are two utility events built in for the common case of animating throwaway entities. While the
tween entity will always despawn when complete, using the DESPAWN_ON_TWEEN_COMPLETE_EVENT user data
will also cause the tween's target to despawn when the tween completes:
fn spawn_a_tweenable(mut commands: Commands) {
commands.spawn((
Transform::default(),
Sprite::default(),
TweenSpriteColour::tween(
Duration::from_millis(200),
Color::WHITE.into(),
Color::WHITE.with_alpha(0.0).into(),
TweenEasing::Linear
)
.with_user_data(DESPAWN_ON_TWEEN_COMPLETE_EVENT)
.spawn()
));
}
While less common, it can also be useful to despawn an entire hierarchy (above and below) when the tween completes (e.g. fading out a UI element that is not a UI root due to styling constraints).
This can be achieved with the DESPAWN_ANCESTORS_ON_TWEEN_COMPLETE_EVENT user data:
fn spawn_a_tweenable_leaf(mut commands: Commands) {
commands.spawn((
Node {
// Some styling
..default()
},
children![(
Text::new("My label"),
TextColor::from(Color::WHITE),
TweenTextColour::tween(
Duration::from_millis(200),
Color::WHITE.into(),
Color::WHITE.with_alpha(0.0).into(),
TweenEasing::Linear
)
.with_user_data(DESPAWN_ANCESTORS_ON_TWEEN_COMPLETE_EVENT)
.spawn()
)]
));
}
WeirdBoi Tween provides a variety of easing functions, forked from bevy_math, made available
under the TweenEasing enum:
By enabling the bevy_defaults feature, you get access to the
TweenTransformTranslation - Tweens a Transform's positionTweenTransformScale - Tweens a Transform's scaleTweenSpriteColor - Tweens a Sprite's colorTweenImageNodeColour - Tweens a Sprite's colorTweenTextColour - Tweens a Sprite's color