Giter Club home page Giter Club logo

bevy_spritesheet_animation's Introduction

Crates.io Docs Build License

bevy_spritesheet_animation is a Bevy plugin for animating sprites that are backed by spritesheets.

An animated character walking from the left to the right and shooting their gun

Features

Quick start

  1. Add the SpritesheetAnimationPlugin to your app
  2. Use the SpritesheetLibrary resource to create new clips and animations
  3. Add a SpritesheetAnimation component to your entity
fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        // Add the plugin to enable animations.
        // This makes the SpritesheetLibrary resource available to your systems.
        .add_plugins(SpritesheetAnimationPlugin)
        .add_systems(Startup, setup)
        .run();
}

fn setup(
    mut commands: Commands,
    mut library: ResMut<SpritesheetLibrary>,
    mut atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
    assets: Res<AssetServer>,
) {
    // Create an animation

    let clip_id = library.new_clip(|clip| {
        // You can configure this clip here (duration, number of repetitions, etc...)

        // This clip will use all the frames in row 3 of the spritesheet
        clip.push_frame_indices(Spritesheet::new(8, 8).row(3));
    });

    let animation_id = library.new_animation(|animation| {
        // You can configure this animation here (duration, number of repetitions, etc...)

        animation.add_stage(clip_id.into());

        // This is a simple animation with a single clip but we can create more sophisticated
        // animations with multiple clips, each one having different parameters.
        //
        // See the `composition` example for more details.
    });

    // Spawn a sprite using Bevy's built-in SpriteBundle

    let texture = assets.load("character.png");

    let layout = atlas_layouts.add(TextureAtlasLayout::from_grid(
        UVec2::new(96, 96),
        8,
        8,
        None,
        None,
    ));

    commands.spawn((
        SpriteBundle {
            texture,
            ..default()
        },
        TextureAtlas {
            layout,
            ..default()
        },
        // Add a SpritesheetAnimation component that references our newly created animation
        SpritesheetAnimation::from_id(animation_id),
    ));

    commands.spawn(Camera2dBundle::default());
}

Overview

Animation clips

An animation clip is a reusable sequence of frames.

It is the most basic building block for creating animations.

Use the SpritesheetLibrary resource to create and configure a new clip.

The clip can then be referenced in any number of animations.

fn setup(mut commands: Commands, mut library: ResMut<SpritesheetLibrary>) {

    // Create a clip that uses some frames from a spritesheet

    let clip_id = library.new_clip(|clip| {
        clip
            .push_frame_indices(Spritesheet::new(8, 8).column(2))
            .set_default_duration(AnimationDuration::PerCycle(1500))
            .set_default_repeat(5);
    });

    // Add this clip to an animation

    let animation_id = library.new_animation(|animation| {
        animation.add_stage(clip_id.into());
    });

    // ... Assign the animation to an entity with the SpritesheetAnimation component ...
}

Animations

In its simplest form, an animation is composed of a single clip that loops endlessly.

However, you're free to compose more sophisticated animations by chaining multiple clips and by tuning the animation parameters.

Use the SpritesheetLibrary resource to create a new animation.

The animation can then be referenced in any number of SpritesheetAnimation component.

fn setup(mut commands: Commands, mut library: ResMut<SpritesheetLibrary>) {

    // ...

    let animation_id = library.new_animation(|animation| {
        let mut stage1 = AnimationStage::from_clip(some_clip_id);
        stage1
            .set_repeat(5)
            .set_easing(Easing::InOut(EasingVariety::Quadratic));

        let mut stage2 = AnimationStage::from_clip(another_clip_id);
        stage2
            .set_duration(AnimationDuration::PerFrame(120))
            .set_direction(Animation::Direction::Backwards);

        animation
            .add_stage(stage1)
            .add_stage(stage2)
            .set_duration(AnimationDuration::PerCycle(1000))
            .set_direction(Animation::Direction::PingPong);
    });

    // ... Assign the animation to an entity with the SpritesheetAnimation component ...
}

Think of clips and animations as assets!

Clips and animations should be created once. You can then assign them to many entities.

❌ BAD

You should not create the same clip/animation for each entity that plays it.

fn spawn_enemies(mut commands: Commands, mut library: ResMut<SpritesheetLibrary>) {

    // Creating identical animations gives more work to the plugin and degrades performance!

    for _ in 0..100 {
        let clip_id = library.new_clip(|clip| { /* ... */ });

        let animation_id = library.new_animation(|animation| { /* ... */ });

        commands.spawn((
            SpriteBundle { /* .... */ },
            SpritesheetAnimation::from_id(animation_id),
        ));
    }
}

πŸ‘ GOOD

Instead, create clips/animations once and then reference them when needed.

For instance, you can create all your animations in a setup system, give them unique names and then assign them to entities at a later stage.

fn create_animation(mut library: ResMut<SpritesheetLibrary>) {

    let clip_id = library.new_clip(|clip| { /* ... */ });

    let animation_id = library.new_animation(|animation| { /* ... */ });

    // Here, we name the animation to make it easy to retrieve it in other systems.
    //
    // Alternatively, you may prefer to store the animation ID yourself.
    // For instance, in a Bevy Resource that contains the IDs of all your clips/animations.
    // Something like:
    //
    // #[derive(Resource)]
    // struct GameAnimations {
    //     enemy_running: AnimationId,
    //     enemy_firing: AnimationId,
    //     ... and so on ...
    // }

    library.name_animation(animation_id, "enemy running");
}

fn spawn_enemies(mut commands: Commands, library: Res<SpritesheetLibrary>) {

    // Retrieve our animation and assign it to many entities

    if let Some(animation_id) = libray.animation_with_name("enemy running") {
        for _ in 0..100 {
            commands.spawn((
                SpriteBundle { /* .... */ },
                SpritesheetAnimation::from_id(animation_id),
            ));
        }
    }
}

3D sprites

A dozen of 3D sprites moving in 3D space

This crate also makes it easy to integrate 3D sprites into your games, which is not supported by Bevy out of the box.

Sprite3dBundle contains all the necesary components to enable 3D sprites. Use Sprite3dBuilder to easily create one of those.

Animating a 3D sprite is the same as animating 2D sprites: simply attach a SpritesheetAnimation component to your entity.

fn spawn_character(mut commands: Commands, mut library: ResMut<SpritesheetLibrary>) {

    let animation_id = library.new_animation(|animation| { /* ... */ });

    commands.spawn((
        Sprite3dBuilder::from_image(texture.clone())
            .with_atlas(atlas_layout_handle)
            .with_anchor(Anchor::BottomRight)
            .build(),
        SpritesheetAnimation::from_id(animation_id)
    ));
}

More examples

For more examples, browse the examples/ directory.

Example Description
basic Shows how to create an animated sprite
3d Shows how to create 3D sprites
composition Shows how to create an animation with multiple stages
parameters Shows the effect of each animation parameter
character Shows how to create a controllable character with multiple animations
events Shows how to react to animations reaching points of interest with events
stress Stress test with thousands of animated sprites

Compatibility

bevy bevy_spritesheet_animation
0.14 0.2.0
0.13 0.1.0

Credits

  • The character spritesheet used for the examples is CC0 from thekingphoenix

bevy_spritesheet_animation's People

Contributors

merwaaan avatar mgi388 avatar blumbye avatar

Stargazers

 avatar hoseine6 avatar  avatar ZXY avatar ahmad avatar zzzz avatar Jacob Dufault avatar Michael Knopke avatar Josef21296 avatar  avatar  avatar Adrien Bon avatar Balazs Horvath avatar Tauseef Khan avatar Harold Hersey avatar  avatar Diego Abad avatar James Gayfer avatar MΓ‘rio Feroldi avatar Antonio Farinetti avatar Will Hart avatar zhowdan avatar Georges KABBOUCHI avatar Yan Chen avatar Mateo avatar Stefano Musumeci avatar wood avatar MevLyshkin avatar  avatar  avatar  avatar

Watchers

Balazs Horvath avatar  avatar

bevy_spritesheet_animation's Issues

3d: frame rate drops significantly with a lot of sprites possibly due to StandardMaterial not being reused

Every sprite entity gets a new material even:

commands.entity(entity).insert(materials.add(material));

I think this causes the frame rate to drop.

In a similar way to what is done in bevy_sprite3d, https://github.com/FraserLee/bevy_sprite3d/blob/ce57326ff63b1dae99cd2595422672bc82b22325/src/lib.rs#L297-L309, I think we need to use a material cache and return that handle if all of our props match.

It can be done in a separate issue, but if you do go down this path, it would be good to support unlit and alpha_mode from the outside, and so from the beginning make those part of the material key.

I haven't tried to change this locally to add a cache, so it's possible this isn't going to help, but even in other Bevy examples where I spawn identical 100 cubes and incorrectly create a new material for every cube I instantly see a frame rate drop. When I change to reuse the same material handle, the frame rate comes back up.

Thoughts on using std::time::Duration instead of u32 + "milliseconds" in docs

E.g. in

pub enum AnimationDuration {
/// Specifies the duration of each frame in milliseconds
PerFrame(u32),
/// Specifies the duration of one animation cycle in milliseconds
PerCycle(u32),
}

Callers rely on reading the docs to see that the duration needs to be milliseconds (mistakes are possibly easier to make, too).

Would it be worth using https://doc.rust-lang.org/std/time/struct.Duration.html? This would make callers look something like:

let clip_id = library.new_clip(|clip| {
    clip..set_default_duration(AnimationDuration::PerFrame(Duration::from_millis(100)));
});

(untested).

I know it's more verbose, but it takes the hard-coded millis out of the crate and (arguably) makes the crate less opinionated on the duration format.

SpritesheetLibrary::new_clip

Had some issues with this method.

  • You cannot make marker IDs in the method because the library is borrowed as mutable to call new_clip. A reference to the library from within the closure would solve that.
  • You cannot access anything mutably in the closure since it's a Fn method. It should only need to be a FnOnce.

Ability to start an animation from a specific frame.

I am currently using this library to orchestrate a set of directional animations, and it is great at what it does.

I have one question tho, regarding the usage of the library - I'd like to not only change the animation - but also be able to change the frame the animation starts on, this is specifically important for things like bullet animations, where one would maybe want to randomize the starting sprite, or - in my case - the animation of a character rolling, changing the direction of the roll results in the whole animation restarting, which can be quite immersion breaking.

Any ideas how one might be able to solve this?

Support variants / frame sets

I want to be able to create an animation which is made up of a bunch of frames, but which has a number of variants of those same frames.

This is for billboard sprites in a 3D game where each variant is for one of 8 directions.

So if you have a walk animation made up of 3 frames, you want to pick the 3 frames for the current variant.

Calling systems can set the current variant every frame (if they like). For example, a system might update the current variant to match the relative direction of the sprite to the camera^.

I don't think it's possible to use the current features of the crate for one main reason. We need to preserve the existing animation and state rather than asking callers changing the animation_id or inserting a new animation so that the animation doesn't reset. Imagine a walk animation is playing. If the camera instantaneously rotates, you want the character's same foot to remain on the ground. I.e. you want to keep playing your walk animation, you just want to switch variants.

I am not sure if there's an industry term for this, but I'm using the term variants. Frame sets may also be a term for this, I'm not sure.

Are you interested in working on a design for this with me?

^ Simplified system that updates the sprite's animation variant to correspond with the camera's transform relative to the sprite

fn update_sprite_directions(
    // ...
) {
    for (mut animation) in q.iter_mut() {
        let direction_count = animation.variant_count as f32;

        let direction = 0.0 // calculate direction ...

        // Callers just need to update the variant they want to show.
        animation.current_variant = direction.floor() as usize;
    }
}

Resetting animations

Currently it does not seem possible to reset animations on an entity (neither reset in-progress animations, nor restart animations that ended). It looks like this could be done by making SpritesheetAnimator public and adding a reset_animation(entity) method.

Asset loader

By any chance have you created an asset loader that works with this crate?

It's possible that the space is too opinionated at least for this crate to provide an asset loader, but I was curious if you've been using this crate with an asset loader?

I wonder if at the very least we need serde support so we can create clips and animations from RON files, for example? Again, I guess it's possible that is something that this crate should not support and those serialization types and the loader should all be created as 3rd party to this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.