Skip to content

Commit

Permalink
feat!: The SpriteSheetAnimation is now an asset (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcornaz committed Jul 12, 2021
1 parent 8b2fb36 commit 2a895a5
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 90 deletions.
16 changes: 11 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ repository = "https://github.com/jcornaz/benimator"
keywords = ["game", "gamedev", "anmiation", "bevy"]
categories = ["game-development"]

[features]
default = ["warnings"]
warnings = ["bevy_log"]

[dependencies]
bevy_core = "^0.5.0"
bevy_ecs = "^0.5.0"
bevy_app = "^0.5.0"
bevy_reflect = "^0.5.0"
bevy_sprite = "^0.5.0"
bevy_core = { version = "^0.5.0", default-features = false }
bevy_ecs = { version = "^0.5.0", default-features = false }
bevy_app = { version = "^0.5.0", default-features = false }
bevy_reflect = { version = "^0.5.0", default-features = false }
bevy_sprite = { version = "^0.5.0", default-features = false }
bevy_asset = { version = "^0.5.0", default-features = false }
bevy_log = { version = "^0.5.0", default-features = false, optional = true }

[dev-dependencies]
bevy = { version = "^0.5.0", default-features = false, features = ["render", "bevy_wgpu", "x11", "png"] }
Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,27 @@ fn spawn(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut textures: ResMut<Assets<TextureAtlas>>,
mut animations: ResMut<Assets<SpriteSheetAnimation>>,
) {
// Don't forget the camera ;-)
commands.spawn_bundle(OrthographicCameraBundle::new_2d());

// Create an animation
// Here we use an index-range (from 0 to 4) where each frame has the same duration
let animation_handle = animations.add(SpriteSheetAnimation::from_range(
0..=4,
Duration::from_millis(100),
));

commands
// Spawn a bevy sprite-sheet
.spawn_bundle(SpriteSheetBundle {
texture_atlas: textures.add(TextureAtlas::from_grid(asset_server.load("coin.png"), Vec2::new(16.0, 16.0), 5, 1)),
transform: Transform::from_scale(Vec3::splat(10.0)),
..Default::default()
})
// Insert the animation component
// Here we use an index-range (from 0 to 4) where each frame has the same duration
.insert(SpriteSheetAnimation::from_range(0..=4, Duration::from_millis(100)))
// Insert the asset handle of the animation
.insert(animation_handle)
// Start the animation immediately. Remove this component in order to pause the animation.
.insert(Play);
}
Expand All @@ -71,6 +78,10 @@ Add to `Cargo.toml`:
benimator = "0.2.0"
```

## Cargo features

* `warnings` (enabled by default). Log warnings in case of incorrect usage detected.

## Bevy Version Compatibility

| bevy | benimator |
Expand Down
79 changes: 44 additions & 35 deletions examples/change_animation.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,50 @@
use core::ops::Deref;
use std::time::Duration;

use bevy::prelude::*;

use benimator::*;

static FAST: Duration = Duration::from_millis(100);
static SLOW: Duration = Duration::from_millis(250);
// Create a resource to store handles of the animations
#[derive(Default)]
struct Animations {
slow: Handle<SpriteSheetAnimation>,
fast: Handle<SpriteSheetAnimation>,
}

fn main() {
App::build()
.init_resource::<Animations>()
.add_plugins(DefaultPlugins)
.add_plugin(AnimationPlugin) // <-- Add the plugin
.add_startup_system(spawn.system())
.add_system(animation.system())
.add_plugin(AnimationPlugin)
.add_startup_system_to_stage(StartupStage::PreStartup, create_animations.system())
.add_startup_system(spawn_animated_coin.system())
.add_startup_system(spawn_camera.system())
.add_system(change_animation.system())
.run();
}

fn spawn(
fn create_animations(
mut handles: ResMut<Animations>,
mut assets: ResMut<Assets<SpriteSheetAnimation>>,
) {
handles.fast = assets.add(SpriteSheetAnimation::from_range(
0..=4,
Duration::from_millis(100),
));
handles.slow = assets.add(SpriteSheetAnimation::from_range(
0..=4,
Duration::from_millis(250),
));
}

fn spawn_animated_coin(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut textures: ResMut<Assets<TextureAtlas>>,
animations: Res<Animations>,
) {
// Don't forget the camera ;-)
commands.spawn_bundle(OrthographicCameraBundle::new_2d());

commands
// Spawn a bevy sprite-sheet
.spawn_bundle(SpriteSheetBundle {
texture_atlas: textures.add(TextureAtlas::from_grid(
asset_server.load("coin.png"),
Expand All @@ -36,38 +55,28 @@ fn spawn(
transform: Transform::from_scale(Vec3::splat(10.0)),
..Default::default()
})
// Insert the animation component
// Here we use an index-range (from 0 to 4) where each frame has the same duration
.insert(SpriteSheetAnimation::from_range(0..=4, FAST))
// Start the animation immediately. Remove this component in order to pause the animation.
.insert(animations.fast.clone())
.insert(Play)
// Add component and timer to query
.insert(Animation::Fast)
// Add timer, counting down the time before the animation is changed
.insert(Timer::from_seconds(5.0, true));
}

enum Animation {
Fast,
Slow,
}

fn animation(
fn change_animation(
time: Res<Time>,
mut query: Query<(&mut Timer, &mut Animation, &mut SpriteSheetAnimation)>,
animations: Res<Animations>,
mut query: Query<(&mut Timer, &mut Handle<SpriteSheetAnimation>)>,
) {
if let Ok((mut timer, mut animation, mut sprite_sheet_animation)) = query.single_mut() {
timer.tick(time.delta());
if timer.finished() {
match *animation {
Animation::Fast => {
*animation = Animation::Slow;
*sprite_sheet_animation = SpriteSheetAnimation::from_range(0..=4, SLOW);
}
Animation::Slow => {
*animation = Animation::Fast;
*sprite_sheet_animation = SpriteSheetAnimation::from_range(0..=4, FAST);
}
if let Ok((mut timer, mut animation)) = query.single_mut() {
if timer.tick(time.delta()).finished() {
if animation.deref() == &animations.fast {
*animation = animations.slow.clone();
} else {
*animation = animations.fast.clone();
}
}
}
}

fn spawn_camera(mut commands: Commands) {
commands.spawn_bundle(OrthographicCameraBundle::new_2d());
}
16 changes: 10 additions & 6 deletions examples/readme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,18 @@ fn spawn(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut textures: ResMut<Assets<TextureAtlas>>,
mut animations: ResMut<Assets<SpriteSheetAnimation>>,
) {
// Don't forget the camera ;-)
commands.spawn_bundle(OrthographicCameraBundle::new_2d());

// Create an animation
// Here we use an index-range (from 0 to 4) where each frame has the same duration
let animation_handle = animations.add(SpriteSheetAnimation::from_range(
0..=4,
Duration::from_millis(100),
));

commands
// Spawn a bevy sprite-sheet
.spawn_bundle(SpriteSheetBundle {
Expand All @@ -32,12 +40,8 @@ fn spawn(
transform: Transform::from_scale(Vec3::splat(10.0)),
..Default::default()
})
// Insert the animation component
// Here we use an index-range (from 0 to 4) where each frame has the same duration
.insert(SpriteSheetAnimation::from_range(
0..=4,
Duration::from_millis(100),
))
// Insert the asset handle of the animation
.insert(animation_handle)
// Start the animation immediately. Remove this component in order to pause the animation.
.insert(Play);
}
7 changes: 4 additions & 3 deletions src/animation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ use std::ops::RangeInclusive;
use std::time::Duration;

use bevy_ecs::prelude::*;
use bevy_reflect::prelude::*;
use bevy_reflect::{Reflect, TypeUuid};

/// Component to animate the `TextureAtlasSprite` of the same entity
/// Asset that define an animation of `TextureAtlasSprite`
///
/// See crate level documentation for usage
#[derive(Debug, Clone, Default, Reflect)]
#[derive(Debug, Clone, Default, Reflect, TypeUuid)]
#[reflect(Component)]
#[uuid = "6378e9c2-ecd1-4029-9cd5-801caf68517c"]
pub struct SpriteSheetAnimation {
/// Frames
pub frames: Vec<Frame>,
Expand Down
46 changes: 26 additions & 20 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,28 @@
//! fn spawn() { /* ... */ }
//! ```
//!
//! 2. Insert the [`SpriteSheetAnimation`] component to the sprite sheets you want to animate
//! 2. Create a [`SpriteSheetAnimation`] and insert the asset handle to the sprite sheet entity you want to animate
//!
//! ```
//! # use std::time::Duration;
//! # use bevy::prelude::*;
//! # use benimator::*;
//!
//! fn spawn(mut commands: Commands) {
//! fn spawn(mut commands: Commands, mut animations: ResMut<Assets<SpriteSheetAnimation>>) {
//!
//! // Create an animation
//! let animation_handle = animations.add(SpriteSheetAnimation::from_range(
//! 0..=2, // Indices of the sprite atlas
//! Duration::from_secs_f64(1.0 / 12.0), // Duration of each frame
//! ));
//!
//! commands
//! .spawn_bundle(SpriteSheetBundle {
//! // TODO: Configure the sprite sheet
//! ..Default::default()
//! })
//! // Insert the animation component
//! .insert(SpriteSheetAnimation::from_range(
//! 0..=2, // Indices of the sprite atlas
//! Duration::from_secs_f64(1.0 / 12.0), // Duration of each frame
//! ))
//! // Insert the asset handle of the animation
//! .insert(animation_handle)
//! // Start the animation immediately
//! .insert(Play);
//! }
Expand All @@ -59,28 +63,23 @@
//!
//! ```
//! # use std::time::Duration;
//! # use bevy::prelude::*;
//! # use benimator::*;
//! # fn spawn(mut commands: Commands) {
//! commands
//! .spawn_bundle(SpriteSheetBundle { ..Default::default() })
//! .insert(
//! SpriteSheetAnimation::from_range(0..=2, Duration::from_millis(100))
//! .once() // <-- Runs the animation only once
//! )
//! .insert(Play); // <-- This component will be automatically removed once the animation is finished
//! # }
//! SpriteSheetAnimation::from_range(0..=2, Duration::from_millis(100))
//! .once(); // <-- Runs the animation only once
//! ```
//!
//! Note that, for animations that run once, the `Play` component is automatically removed when the animation is done.
//! So you can use the `RemovedComponents<Play>` system parameter to execute logic at the end of the animation.
//!
//! ## Play/Pause
//!
//! Animations proceed only if the [`Play`] component is in the entity.
//! Animations proceed only if the [`Play`] component is present in the entity.
//!
//! To pause or resume an animation, simply remove/insert the [`Play`] component.
//!
//! ## Fine-grained frame-duration
//!
//! For more precise configuration, it is possible to define the duration of each frame:
//! For a more precise configuration, it is possible to define the duration of each frame:
//!
//! ```rust
//! # use benimator::*;
Expand All @@ -97,6 +96,7 @@
extern crate rstest;

use bevy_app::prelude::*;
use bevy_asset::AddAsset;
use bevy_ecs::prelude::*;
use bevy_reflect::Reflect;

Expand All @@ -105,6 +105,9 @@ pub use animation::{AnimationMode, Frame, SpriteSheetAnimation};
mod animation;
mod state;

#[cfg(feature = "warnings")]
mod warnings;

/// Plugin to enable sprite-sheet animation
///
/// See crate level documentation for usage
Expand All @@ -129,8 +132,11 @@ pub struct Play;

impl Plugin for AnimationPlugin {
fn build(&self, app: &mut AppBuilder) {
app.register_type::<SpriteSheetAnimation>()
app.add_asset::<SpriteSheetAnimation>()
.add_system_set(state::update_systems())
.add_system_to_stage(CoreStage::PostUpdate, state::post_update_system());

#[cfg(feature = "warnings")]
app.add_system_set(warnings::systems());
}
}
25 changes: 18 additions & 7 deletions src/state.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::ops::DerefMut;
use std::time::Duration;

use bevy_asset::{Assets, Handle};
use bevy_core::prelude::*;
use bevy_ecs::prelude::*;
use bevy_sprite::prelude::*;
Expand Down Expand Up @@ -64,7 +65,7 @@ fn insert(
'_,
Entity,
(
With<SpriteSheetAnimation>,
With<Handle<SpriteSheetAnimation>>,
Without<SpriteSheetAnimationState>,
),
>,
Expand All @@ -76,8 +77,11 @@ fn insert(
}
}

fn remove(mut commands: Commands<'_>, query: RemovedComponents<'_, SpriteSheetAnimation>) {
for entity in query.iter() {
fn remove(
mut commands: Commands<'_>,
removed: RemovedComponents<'_, Handle<SpriteSheetAnimation>>,
) {
for entity in removed.iter() {
commands
.entity(entity)
.remove::<SpriteSheetAnimationState>();
Expand All @@ -87,20 +91,27 @@ fn remove(mut commands: Commands<'_>, query: RemovedComponents<'_, SpriteSheetAn
fn animate(
mut commands: Commands<'_>,
time: Res<'_, Time>,
animation_defs: Res<'_, Assets<SpriteSheetAnimation>>,
mut animations: Query<
'_,
(
Entity,
&mut TextureAtlasSprite,
&SpriteSheetAnimation,
&Handle<SpriteSheetAnimation>,
&mut SpriteSheetAnimationState,
),
With<Play>,
>,
) {
for (entity, sprite, animation, mut state) in animations
.iter_mut()
.filter(|(_, _, anim, _)| anim.has_frames())
for (entity, sprite, animation, mut state) in
animations
.iter_mut()
.filter_map(|(entity, sprite, anim_handle, state)| {
animation_defs
.get(anim_handle)
.filter(|anim| anim.has_frames())
.map(|anim| (entity, sprite, anim, state))
})
{
if state.update(sprite, animation, time.delta()) {
commands.entity(entity).remove::<Play>();
Expand Down
Loading

0 comments on commit 2a895a5

Please sign in to comment.