bevy_animations 0.1.3

2d Game Animation Engine built for Bevy
Documentation

bevy_animations is a Lightweight 2d animations engine built for Bevy

What bevy_animations accomplishes

  1. using bevy_animations is easy and the animation configurations are simple

  2. bevy_animations is fast enough to handle all of the entities you want animated

Add bevy_animations to your Bevy App

use bevy_animations::AnimationsPlugin;
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(AnimationsPlugin {
            pixels_per_meter: 20. // your desired pixels_per_meter
        })
        .run()
}

How bevy_animations animations work

  • they work off of left to right based animations from sprite sheets
  • specified timings or meters_per_frame for each frame
  • user defining which y indexes are left, right, up and down facing sprites
  • timed animations can block others from happening
  • utilizing a priortity based system so you can define multiple blocking animations with different priorities to render

How to define a bevy_animations animation

You first need to spawn an entity using Commands like this

use bevy_animations::*;
use bevy::prelude::*;

fn entity_setup(
    mut commands: Commands,
    animations: ResMut<Animations>
) {
    let entity = commands.spawn(
        AnimationDirection::Still // the `AnimationDirection` component is needed on the entity to determine the direction
        SpriteSheetBundle {
            texture_atlas: // your sprite sheet handle
            transform: Transform::from_xyz(0., 0., 0.) // your desired location in the `World`
        }
        /* The rest of your entity configuration */
    );
}

You can then add your animations to ResMut<Animations> like this

animations.insert_animation(
    entity.id(), // the entity is needed to determine which `Handle<TextureAtlas>` is being manipulated
    AnimationType::Transform(
        TransformAnimation::new(
            /* animation_frames */ vec![0, 1, 2, 3] // the x index for your frames to cycle through
            /* meters_per_frame */ 0.55 // your desired meters per frame
            /* handle */ texture_atlas_hanle // your sprite sheet
            /* frame */ Vec2::new(4., 4.) // the length and height of your sprite sheet
            /* direction_indexes */ AnimationDirectionIndexes::new(4, 3, 2, 1) // from the example above
            /* repeating */ true // if the animation is repeating or not
        )
    ),
    "player_running" // the name of the animation. will be used when sending an `AnimationEvent`
)

You can also add a TimedAnimation like this

animations.insert_animation(entity.id(), AnimationType::Timed(
    TimedAnimation::new(
        /* animation_frames */ vec![0, 1, 2, 3] // the x index for your frames to cycle through, 
        /* frame_timings_in_secs */ vec![0.001, 0.300, 0.300, 0.250], // Note that the the first timing is set to 0.001 so the animation starts immediately. If this value doesn't suit your needs, you can change it to another parameter.
        /* handle */ texture_atlas_hanle // your sprite sheet
        /* frame */ Vec2::new(4., 4.) // the length and height of your sprite sheet 
        /* direction_indexes */ AnimationDirectionIndexes::new(4, 3, 2, 1) // from the example above
        /* repeating */ true // if the animation is repeating or not
        /* blocking */ true, // if the animation should block others
        /* blocking_priority */ 1 // the priority for which animation should block other blocking animations
    ),
    "player_die" // the name of the animation. will be used when sending an `AnimationEvent`
))

We can then start an animation by sending it over an EventWriter<AnimationEvent> like this

fn move_player(
    mut event_writer: EventWriter<AnimationEvent>
) {
    // your move logic here
    event_writer.send(AnimationEvent("player_running", entity));
}
  • Note that you can send an event of the same name multiple times even during animation without ruining it

  • Note if you send an event with a different name the current animation of the entity will change immediately.

Knowing this you can change the player_running animation to player_die in another system where you could check collisions like this

fn check_collisions(
    mut commands: Commands,
    rapier_context: Res<RapierContext> // great 2d physics engine for lots of things we are using it for collision detection
    mut event_writer: EventWriter<AnimationEvent>
) {
    for pair in rapier_context.contact_pairs() {
        if pair.has_any_active_contacts() {

            let entity = pair.collider1();
            
            // send the event for the animating entity
            event_writer.send(AnimationEvent("player_die", entity));
            // despawn the entity after death
            commands.entity(entity).despawn();
            return;
        }
    }
}
  • Note that bevy_animations will automatically remove your entity from it's own data structure if it doesn't exist in the World i.e when the entity despawns via .despawn()

  • Note there is no functionality internally yet for doing a task like despawning an entity only after an animation is finished. This can be accomplished on your own however.

Open Source

bevy_animations is open-source forever. You can contribute via the GitHub Repo