#tweening #component #system #events #project #despawn #ping-pong #completion #simultaneously

weirdboi_tween

Relationship based component value tweening for Bevy projects

1 unstable release

Uses new Rust 2024

new 0.1.0 May 4, 2025

#1 in #simultaneously

Apache-2.0

38KB
920 lines

WeirdBoi Tween

Crates.io License

A component value tweening library for Bevy. Tweens become first class entities, working via the new relationships system.

Features

  • Relationship-based tweening system
  • Tween any value of any component
  • Apply multiple tweens of the same type simultaneously
  • Support for tween iteration (Once, Loop, PingPong)
  • User data events for looping & completion

Installation

Add the following to your Cargo.toml:

[dependencies]
weirdboi_tween = "0.1.0"

Basic Usage

  1. Setup
  2. Tweening Components
  3. Creating Tweenables
  4. Tween Modes
  5. Tween User Events
  6. Built in Events

Setup

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();
}

Tweening Component Properties

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,
            ),
        )])
    ));
}

Creating custom Tweenables

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,
        ),
    ))
}

Tween Modes

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 stops
  • Loop: 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, 3
  • PingPong: 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, 3

Tween Events

Any 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
        }
    }
}

Automatic Despawn

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()
        )]
    ));
}

Available Easing Functions

WeirdBoi Tween provides a variety of easing functions, forked from bevy_math, made available under the TweenEasing enum:

  • Linear
  • 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

Built-in Tweenable Types

By enabling the bevy_defaults feature, you get access to the

  • TweenTransformTranslation - Tweens a Transform's position
  • TweenTransformScale - Tweens a Transform's scale
  • TweenSpriteColor - Tweens a Sprite's color
  • TweenImageNodeColour - Tweens a Sprite's color
  • TweenTextColour - Tweens a Sprite's color

Dependencies

~17–53MB
~1M SLoC