Skip to content

Commit

Permalink
feat(unstable): animation asset loader (#61)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcornaz committed Jun 5, 2022
1 parent 5ddbe2f commit 4d0ae1c
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 32 deletions.
8 changes: 7 additions & 1 deletion Cargo.toml
Expand Up @@ -12,7 +12,7 @@ categories = ["game-development"]

[features]
default = []
unstable-load-from-file = ["serde", "serde_yaml"]
unstable-load-from-file = ["serde", "serde_yaml", "anyhow", "bevy_utils"]

[dependencies]
bevy_core = { version = "0.7.0", default-features = false }
Expand All @@ -21,8 +21,10 @@ bevy_app = { version = "0.7.0", default-features = false }
bevy_reflect = { version = "0.7.0", default-features = false }
bevy_sprite = { version = "0.7.0", default-features = false }
bevy_asset = { version = "0.7.0", default-features = false }
bevy_utils = { version = "0.7.0", default-features = false, optional = true }
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
serde_yaml = { version = "0.8.24", default-features = false, optional = true }
anyhow = { version = "1.0", default-features = false, optional = true }

[dev-dependencies]
bevy = { version = "0.7.0", default-features = false, features = ["render", "x11", "png"] }
Expand All @@ -33,5 +35,9 @@ criterion = { version = "0.3.5", default-features = false }
name = "play_component"
harness = false

[[example]]
name = "using_animation_file"
required-features = ["unstable-load-from-file"]

[profile.dev]
debug = 0
12 changes: 12 additions & 0 deletions assets/coin.animation.yml
@@ -0,0 +1,12 @@
mode: repeat
frames:
- index: 0
duration: 100
- index: 1
duration: 100
- index: 2
duration: 100
- index: 3
duration: 100
- index: 4
duration: 100
41 changes: 41 additions & 0 deletions examples/using_animation_file.rs
@@ -0,0 +1,41 @@
use bevy::prelude::*;

use benimator::*;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(AnimationPlugin::default()) // <-- Add the plugin
.add_startup_system(spawn)
.run();
}

fn spawn(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut textures: ResMut<Assets<TextureAtlas>>,
) {
// 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: Handle<SpriteSheetAnimation> = asset_server.load("coin.animation.yml");

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 asset handle of the animation
.insert(animation_handle)
// Start the animation immediately. Remove this component in order to pause the animation.
.insert(Play);
}
29 changes: 3 additions & 26 deletions src/animation.rs
@@ -1,6 +1,9 @@
#[cfg(feature = "unstable-load-from-file")]
mod parse;

#[cfg(feature = "unstable-load-from-file")]
pub(crate) mod load;

use std::{ops::RangeInclusive, time::Duration};

use bevy_reflect::TypeUuid;
Expand Down Expand Up @@ -138,32 +141,6 @@ impl SpriteSheetAnimation {
pub(crate) fn has_frames(&self) -> bool {
!self.frames.is_empty()
}

/// Parse content of a yaml file representing the animation
///
/// # Yaml schema
///
/// ```yaml
/// # The mode can be one of: 'once', 'repeat', 'ping-pong'
/// # or 'repeatFrom(n)' (where 'n' is the frame-index to repeat from)
/// # The default is 'repeat'
/// mode: ping-pong
/// frames:
/// - index: 0 # index in the sprite sheet for that frame
/// duration: 100 # duration of the frame in milliseconds
/// - index: 1
/// duration: 100
/// - index: 2
/// duration: 120
/// ```
///
/// # Errors
///
/// Returns an error if the content is not a valid yaml representation of an animation
#[cfg(feature = "unstable-load-from-file")]
pub fn from_yaml(yaml: &str) -> Result<Self, AnimationParseError> {
serde_yaml::from_str(yaml).map_err(AnimationParseError)
}
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
Expand Down
24 changes: 24 additions & 0 deletions src/animation/load.rs
@@ -0,0 +1,24 @@
use super::SpriteSheetAnimation;
use bevy_asset::{AssetLoader, LoadContext, LoadedAsset};
use bevy_utils::BoxedFuture;

#[derive(Debug, Default)]
pub(crate) struct SpriteSheetAnimationLoader;

impl AssetLoader for SpriteSheetAnimationLoader {
fn load<'a>(
&'a self,
bytes: &'a [u8],
load_context: &'a mut LoadContext<'_>,
) -> BoxedFuture<'a, Result<(), anyhow::Error>> {
Box::pin(async move {
let custom_asset = SpriteSheetAnimation::from_yaml_bytes(bytes)?;
load_context.set_default_asset(LoadedAsset::new(custom_asset));
Ok(())
})
}

fn extensions(&self) -> &[&str] {
&["animation.yml", "animation.yaml"]
}
}
66 changes: 61 additions & 5 deletions src/animation/parse.rs
Expand Up @@ -6,8 +6,64 @@ use std::{

use serde::{de, Deserialize, Deserializer};

use crate::SpriteSheetAnimation;

use super::Mode;

impl SpriteSheetAnimation {
/// Parse content of a yaml string representing the animation
///
/// # Yaml schema
///
/// ```yaml
/// # The mode can be one of: 'once', 'repeat', 'ping-pong'
/// # or 'repeatFrom(n)' (where 'n' is the frame-index to repeat from)
/// # The default is 'repeat'
/// mode: ping-pong
/// frames:
/// - index: 0 # index in the sprite sheet for that frame
/// duration: 100 # duration of the frame in milliseconds
/// - index: 1
/// duration: 100
/// - index: 2
/// duration: 120
/// ```
///
/// # Errors
///
/// Returns an error if the content is not a valid yaml representation of an animation
#[cfg(feature = "unstable-load-from-file")]
pub fn from_yaml_str(yaml: &str) -> Result<Self, AnimationParseError> {
serde_yaml::from_str(yaml).map_err(AnimationParseError)
}

/// Parse content of a yaml bytes representing the animation
///
/// # Yaml schema
///
/// ```yaml
/// # The mode can be one of: 'once', 'repeat', 'ping-pong'
/// # or 'repeatFrom(n)' (where 'n' is the frame-index to repeat from)
/// # The default is 'repeat'
/// mode: ping-pong
/// frames:
/// - index: 0 # index in the sprite sheet for that frame
/// duration: 100 # duration of the frame in milliseconds
/// - index: 1
/// duration: 100
/// - index: 2
/// duration: 120
/// ```
///
/// # Errors
///
/// Returns an error if the content is not a valid yaml representation of an animation
#[cfg(feature = "unstable-load-from-file")]
pub fn from_yaml_bytes(yaml: &[u8]) -> Result<Self, AnimationParseError> {
serde_yaml::from_slice(yaml).map_err(AnimationParseError)
}
}

#[derive(Debug)]
#[non_exhaustive]
pub struct AnimationParseError(pub(super) serde_yaml::Error);
Expand Down Expand Up @@ -107,7 +163,7 @@ mod tests {
duration: 120";

// when
let animation = SpriteSheetAnimation::from_yaml(content).unwrap();
let animation = SpriteSheetAnimation::from_yaml_str(content).unwrap();

// then
assert_eq!(animation.mode, Mode::PingPong);
Expand All @@ -130,7 +186,7 @@ mod tests {
duration: 100";

// when
let animation = SpriteSheetAnimation::from_yaml(content).unwrap();
let animation = SpriteSheetAnimation::from_yaml_str(content).unwrap();

// then
assert_eq!(animation.mode, Mode::RepeatFrom(0));
Expand All @@ -146,7 +202,7 @@ mod tests {
duration: 100";

// when
let animation = SpriteSheetAnimation::from_yaml(content).unwrap();
let animation = SpriteSheetAnimation::from_yaml_str(content).unwrap();

// then
assert_eq!(animation.mode, Mode::RepeatFrom(0));
Expand All @@ -162,7 +218,7 @@ mod tests {
duration: 100";

// when
let animation = SpriteSheetAnimation::from_yaml(content).unwrap();
let animation = SpriteSheetAnimation::from_yaml_str(content).unwrap();

// then
assert_eq!(animation.mode, Mode::Once);
Expand All @@ -180,7 +236,7 @@ mod tests {
duration: 100";

// when
let animation = SpriteSheetAnimation::from_yaml(content).unwrap();
let animation = SpriteSheetAnimation::from_yaml_str(content).unwrap();

// then
assert_eq!(animation.mode, Mode::RepeatFrom(1));
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Expand Up @@ -159,5 +159,8 @@ impl Plugin for AnimationPlugin {
app.add_asset::<SpriteSheetAnimation>()
.add_system_set_to_stage(CoreStage::PreUpdate, state::maintenance_systems())
.add_system_set_to_stage(CoreStage::Update, state::post_update_systems());

#[cfg(feature = "unstable-load-from-file")]
app.init_asset_loader::<animation::load::SpriteSheetAnimationLoader>();
}
}

0 comments on commit 4d0ae1c

Please sign in to comment.