generated from 2ndbeam/bevy-template
116 lines
3.3 KiB
Rust
116 lines
3.3 KiB
Rust
//! Animated sprite module
|
|
|
|
use std::{collections::HashMap, time::Duration};
|
|
|
|
use bevy::prelude::*;
|
|
use bevy_ecs::system::EntityEntryCommands;
|
|
|
|
/// Data for [AnimatedSprite]
|
|
#[derive(Clone, PartialEq, Debug, Default, Reflect)]
|
|
#[reflect(Clone, PartialEq, Debug, Default)]
|
|
pub struct AnimationData {
|
|
/// Handle to sprite image
|
|
pub image: Handle<Image>,
|
|
/// Sprite frames
|
|
pub frames: Handle<TextureAtlasLayout>,
|
|
/// [AnimatedSprite] duration multiplier, usually 1.
|
|
pub duration_multiplier: f32,
|
|
}
|
|
|
|
impl AnimationData {
|
|
/// Construct new [AnimationData] from given [Handle<TextureAtlasLayout>]
|
|
pub fn new(image: Handle<Image>, frames: Handle<TextureAtlasLayout>, duration_multiplier: f32) -> Self {
|
|
Self { image, frames, duration_multiplier }
|
|
}
|
|
}
|
|
|
|
/// Animated sprite with states
|
|
#[derive(Component, Clone, PartialEq, Debug, Reflect)]
|
|
#[reflect(Component, Clone, PartialEq, Debug)]
|
|
#[require(Sprite)]
|
|
pub struct AnimatedSprite {
|
|
data: HashMap<String, AnimationData>,
|
|
current: Option<AnimationData>,
|
|
timer: Timer,
|
|
duration: Duration,
|
|
}
|
|
|
|
impl AnimatedSprite {
|
|
/// Constructs new AnimatedSprite
|
|
pub fn new(data: HashMap<String, AnimationData>, duration: Duration) -> Self {
|
|
let timer = Timer::new(duration, TimerMode::Once);
|
|
|
|
Self { data, current: None, timer, duration }
|
|
}
|
|
|
|
/// Add new animation to sprite
|
|
pub fn add(&mut self, data: AnimationData, label: String) {
|
|
self.data.insert(label, data);
|
|
}
|
|
|
|
/// Sets internal duration
|
|
pub fn set_duration(&mut self, duration: Duration) {
|
|
self.duration = duration;
|
|
}
|
|
|
|
/// Finds animation by its label and returns it if started playing
|
|
pub fn play(
|
|
&mut self,
|
|
label: &str,
|
|
mode: TimerMode,
|
|
mut sprite: EntityEntryCommands<Sprite>,
|
|
) -> Option<&AnimationData> {
|
|
self.data.get(label).map(|data| {
|
|
self.current = Some(data.clone());
|
|
self.timer = Timer::new(self.duration.mul_f32(data.duration_multiplier), mode);
|
|
|
|
let sprite_image = data.image.clone();
|
|
let layout = data.frames.clone();
|
|
|
|
sprite.and_modify(move |mut sprite| {
|
|
sprite.image = sprite_image;
|
|
sprite.texture_atlas = Some(TextureAtlas { layout, index: 0 });
|
|
});
|
|
|
|
data
|
|
})
|
|
}
|
|
|
|
/// Updates animation timer
|
|
pub fn tick(&mut self, dt: Duration) {
|
|
self.timer.tick(dt);
|
|
}
|
|
|
|
/// Returns inner [Timer::fraction]
|
|
pub fn fraction(&self) -> f32 {
|
|
self.timer.fraction()
|
|
}
|
|
}
|
|
|
|
/// Updates [AnimatedSprite] entities
|
|
pub fn update_animated_sprites(
|
|
time: Res<Time>,
|
|
atlases: Res<Assets<TextureAtlasLayout>>,
|
|
sprites: Query<(&mut AnimatedSprite, &mut Sprite)>
|
|
) {
|
|
let delta = time.delta();
|
|
|
|
for (mut animation, mut sprite) in sprites {
|
|
let Some(current) = &animation.current else {
|
|
continue;
|
|
};
|
|
let Some(atlas) = atlases.get(¤t.frames) else {
|
|
continue;
|
|
};
|
|
|
|
animation.tick(delta);
|
|
|
|
let max_frames = atlas.len() as f32;
|
|
|
|
let current_frame = (max_frames * animation.fraction()) as usize;
|
|
|
|
if let Some(atlas) = &mut sprite.texture_atlas {
|
|
atlas.index = current_frame;
|
|
}
|
|
}
|
|
}
|