10 releases (1 stable)
new 1.0.0 | Nov 20, 2024 |
---|---|
0.4.3 | Nov 11, 2024 |
0.4.2 | Sep 2, 2024 |
0.4.1 | Aug 30, 2024 |
0.1.0 | Apr 10, 2024 |
#95 in Game dev
245 downloads per month
170KB
2K
SLoC
bevy_spritesheet_animation is a Bevy plugin for animating sprites that are backed by spritesheets.
Features
- Animate 2D and 3D sprites! 🎉
- A single Bevy component to add to your entities to play animations.
- Tunable parameters: duration, repetitions, direction, easing.
- Composable animations from multiple clips.
- Events to react to animations ending or reaching specific points.
- A convenient API to select frames in spritesheets.
[!TIP] This crate is under active development. Please regularly check the CHANGELOG for recent changes.
Quick start
- Add the SpritesheetAnimationPlugin to your app
- Use the AnimationLibrary resource to create new clips and animations
- Add a SpritesheetAnimation component to your entity
use bevy::prelude::*;
use bevy_spritesheet_animation::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
// Add the plugin to enable animations.
// This makes the AnimationLibrary resource available to your systems.
.add_plugins(SpritesheetAnimationPlugin::default())
.add_systems(Startup, setup)
.run();
}
fn setup(
mut commands: Commands,
mut library: ResMut<AnimationLibrary>,
mut atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
assets: Res<AssetServer>,
) {
// Create a clip
let spritesheet = Spritesheet::new(8, 8);
let clip = Clip::from_frames(spritesheet.row(3))
.with_duration(AnimationDuration::PerFrame(150));
let clip_id = library.register_clip(clip);
// Add this clip to an animation
let animation = Animation::from_clip(clip_id)
.with_repetitions(AnimationRepetition::Times(3));
let animation_id = library.register_animation(animation);
// 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(spritesheet.atlas_layout(96, 96));
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
Clips
A clip is a sequence of frames.
It is the most basic building block for creating animations. Simple animations may contain a single clip while more complex animations may contain a sequence of clips.
Parameters like duration, repetitions, direction and easing can be specified.
Use the AnimationLibrary resource to register new clips.
The clip can then be referenced in any number of animations.
fn setup(mut commands: Commands, mut library: ResMut<AnimationLibrary>) {
// Create a clip that uses some frames from a spritesheet
let spritesheet = Spritesheet::new(8, 8);
let clip = Clip::from_frames(spritesheet.column(2))
.with_duration(AnimationDuration::PerRepetition(1500))
.with_repetitions(5);
let clip_id = library.register_clip(clip);
// Add this clip to an animation
let animation = Animation::from_clip(clip_id);
// ...
}
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 tuning their parameters.
Use the AnimationLibrary resource to register new animation.
The animation can then be referenced in any number of SpritesheetAnimation component.
fn setup(mut commands: Commands, mut library: ResMut<AnimationLibrary>) {
// ... omitted: create and register a few clips
let animation = Animation::from_clips([
running_clip_id,
shooting_clip_id,
running_clip_id
])
.with_duration(AnimationDuration::PerRepetition(3000))
.with_direction(Animation::Direction::Backwards);
let animation_id = library.register_animation(animation);
// Assign the animation to an entity with the SpritesheetAnimation component
// ... omitted: load the sprite's texture and create an atlas
commands.spawn((
SpriteBundle {
texture,
..default()
},
TextureAtlas {
layout,
..default()
},
SpritesheetAnimation::from_id(animation_id),
));
}
Think of clips and animations as assets!
Clips and animations should be created once. You can then assign them to many entities.
❌ BAD
Do not create the same clip/animation for each entity that plays it.
fn spawn_enemies(mut commands: Commands, mut library: ResMut<AnimationLibrary>) {
// Creating identical animations gives more work to the plugin and degrades performance!
for _ in 0..100 {
let clip = Clip::from_frames([1, 2, 3]);
let clip_id = library.register_clip(clip);
let animation = Animation::from_clip(clip_id);
let animation_id = library.register_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<AnimationLibrary>) {
let clip = Clip::from_frames([1, 2, 3]);
let clip_id = library.register_clip(clip);
let animation = Animation::from_clip(clip_id);
let animation_id = library.register_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<AnimationLibrary>) {
// 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
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<AnimationLibrary>) {
// ...
let animation_id = library.register_animation(animation);
commands.spawn((
Sprite3dBuilder::from_image(texture.clone())
.with_atlas(atlas_layout)
.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 |
progress | Shows how to control an animation |
composition | Shows how to create an animation with multiple clips |
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 |
headless | Shows how to setup a headless Bevy app without rendering |
stress | Stress test with thousands of animated sprites (either 2D or 3D) |
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
Dependencies
~40–77MB
~1.5M SLoC