#![doc = include_str!("../README.MD")]
#![warn(missing_docs)]
use bevy::prelude::*;
pub mod error;
pub mod multi_layer_sprite;
pub mod prelude;
mod sheet;
pub struct SpriteSheetPlugin;
use crate::prelude::*;
impl Plugin for SpriteSheetPlugin {
fn build(&self, app: &mut App) {
app.init_asset::<Sheet>()
.init_asset_loader::<sheet::SheetLoader>()
.init_asset::<multi_layer_sprite::LayersSource>()
.init_asset_loader::<multi_layer_sprite::LayersSourceLoader>()
.init_asset::<multi_layer_sprite::Configuration>()
.init_asset_loader::<multi_layer_sprite::ConfigurationLoader>()
.add_systems(PreUpdate, (update, update_children));
}
}
#[derive(Debug, Component)]
#[non_exhaustive]
pub struct SpriteStateValue {
state: crate::prelude::SpriteState,
sheet_handle: Handle<Sheet>,
}
impl SpriteStateValue {
pub fn new(
state: crate::prelude::SpriteState,
sheet_handle: &Handle<Sheet>,
) -> SpriteStateValue {
let sheet_handle = sheet_handle.clone();
SpriteStateValue {
state,
sheet_handle,
}
}
pub fn set_state(&mut self, state: SpriteState) {
self.state = state;
}
pub fn update_state(&mut self, f: impl FnOnce(&SpriteState) -> SpriteState) {
self.state = f(&self.state)
}
}
fn update(
mut query: Query<(&SpriteStateValue, &mut Sprite)>,
time: Res<Time>,
sheet_assets: Res<Assets<Sheet>>,
) -> bevy::prelude::Result<()> {
let elapsed = time.elapsed();
for (state, mut sprite) in query.iter_mut() {
if let Some(sheet) = sheet_assets.get(&state.sheet_handle) {
let indices = sheet.get(&state.state);
let index = indices[((elapsed.as_millis() / sheet.animation.as_millis())
% indices.len() as u128) as usize];
let atlas = sprite.texture_atlas.as_mut().unwrap();
atlas.index = index;
}
}
Ok(())
}
fn update_children(
state_query: Query<(&SpriteStateValue, &Children)>,
mut sprite_query: Query<&mut Sprite>,
time: Res<Time>,
sheet_assets: Res<Assets<Sheet>>,
) -> bevy::prelude::Result<()> {
let elapsed = time.elapsed();
for (state, children) in state_query.iter() {
if let Some(sheet) = sheet_assets.get(&state.sheet_handle) {
let indices = sheet.get(&state.state);
let index = indices[((elapsed.as_millis() / sheet.animation.as_millis())
% indices.len() as u128) as usize];
for child in children.iter() {
let mut sprite = sprite_query.get_mut(child)?;
let atlas = sprite.texture_atlas.as_mut().unwrap();
atlas.index = index;
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use bevy::{asset::LoadState, prelude::*};
use std::time::Duration;
fn is_loading(load_state: LoadState) -> bool {
matches!(load_state, LoadState::Loading)
}
#[test]
fn test_loading_asset() {
let mut app = App::new();
app.add_plugins((
MinimalPlugins,
AssetPlugin::default(),
super::SpriteSheetPlugin,
));
let handle: Handle<crate::Sheet> = app.world().resource::<AssetServer>().load("test.sheet");
while is_loading(
app.world()
.resource::<AssetServer>()
.get_load_state(&handle)
.unwrap(),
) {
app.update();
std::thread::sleep(Duration::from_secs(1));
}
let sheet = app
.world()
.resource::<Assets<crate::Sheet>>()
.get(&handle)
.unwrap();
use crate::prelude::{Direction, SpriteState};
assert_eq!(
*sheet.get(&SpriteState::Walk {
direction: Direction::Right
}),
vec![30, 31]
);
assert_eq!(
*sheet.get(&SpriteState::Idle {
direction: Direction::Left
}),
vec![0]
);
assert_eq!(
*sheet.get(&SpriteState::Run {
direction: Direction::Bottom
}),
vec![15, 16, 17]
);
}
#[test]
fn test_changing_texture() {
let mut app = App::new();
app.add_plugins((
MinimalPlugins,
AssetPlugin::default(),
super::SpriteSheetPlugin,
));
app.init_asset::<TextureAtlasLayout>();
app.add_systems(
Startup,
(
|mut commands: Commands,
mut atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
asset_server: Res<AssetServer>| {
let atlas_layout = TextureAtlasLayout::new_empty(UVec2::ZERO);
let atlas_layout = atlas_layouts.add(atlas_layout);
let sheet_handle = asset_server.load("test.sheet");
commands.spawn((
Sprite {
texture_atlas: Some(TextureAtlas {
index: 0,
layout: atlas_layout.clone(),
}),
..Default::default()
},
crate::SpriteStateValue::new(
crate::sheet::SpriteState::Idle {
direction: crate::sheet::Direction::Right,
},
&sheet_handle,
),
));
commands
.spawn((crate::SpriteStateValue::new(
crate::sheet::SpriteState::Idle {
direction: crate::sheet::Direction::Top,
},
&sheet_handle,
),))
.with_child(Sprite {
texture_atlas: Some(TextureAtlas {
index: 0,
layout: atlas_layout,
}),
..Default::default()
});
},
),
);
app.update();
app.add_systems(
Update,
(
|query: Query<(&Sprite, &crate::SpriteStateValue)>| {
for (sprite, _) in query.iter() {
assert_eq!(sprite.texture_atlas.as_ref().unwrap().index, 1);
}
},
|state_query: Query<(&crate::SpriteStateValue, &Children)>,
sprite_query: Query<&Sprite>| {
for (_, children) in state_query.iter() {
for child in children.iter() {
let sprite = sprite_query.get(child).unwrap();
assert_eq!(sprite.texture_atlas.as_ref().unwrap().index, 2);
}
}
},
),
);
app.update();
}
}