Skip to content
Snippets Groups Projects
README.md 7.48 KiB
Newer Older
# WeirdBoi Tween

[![Crates.io](https://img.shields.io/crates/v/weirdboi_tween.svg)](https://crates.io/crates/weirdboi_tween)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](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`:

```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:

```rust
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.

```rust

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.

```rust
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.

```rust
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:

```rust
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:

```rust
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