use std::ops::{Deref, DerefMut}; use std::time::Duration; use anyhow::{Error, Result}; use bevy::ecs::system::SystemParam; use bevy::prelude::*; use crate::definitions::{ AnimationFrames, AnimationMode, AnimationOverride, AnimationPaused, AnimationSet, AnimationStatus, HasAnimations, HasDirectionalityAnimation, HasSimpleAnimations, SyncAnimationsToParent, }; use crate::directionality::{Directionality, Horizontal, Vertical}; use crate::systems::AnimationCompleted; /// Manage animated entities #[derive(SystemParam)] pub struct AnimationQuery<'w, 's> { commands: Commands<'w, 's>, animations: Res<'w, Assets<AnimationSet>>, inner: Query< 'w, 's, ( Entity, &'static Handle<AnimationSet>, &'static mut AnimationMode, &'static mut AnimationStatus, ), ( Or<(With<HasAnimations>, With<HasDirectionalityAnimation>)>, Without<HasSimpleAnimations>, Without<SyncAnimationsToParent>, ), >, inner_child: Query< 'w, 's, ( Entity, &'static Parent, &'static Handle<AnimationSet>, &'static mut AnimationStatus, ), ( With<SyncAnimationsToParent>, Without<HasAnimations>, Without<HasDirectionalityAnimation>, Without<HasSimpleAnimations>, ), >, direction: Query<'w, 's, &'static mut Directionality>, action_animation: Query<'w, 's, &'static mut AnimationOverride>, tile_sprite: Query<'w, 's, &'static mut TextureAtlasSprite>, paused: Query<'w, 's, Entity, With<AnimationPaused>>, events: EventWriter<'w, 's, AnimationCompleted>, } impl<'w, 's> Deref for AnimationQuery<'w, 's> { type Target = Query< 'w, 's, ( Entity, &'static Handle<AnimationSet>, &'static mut AnimationMode, &'static mut AnimationStatus, ), ( Or<(With<HasAnimations>, With<HasDirectionalityAnimation>)>, Without<HasSimpleAnimations>, Without<SyncAnimationsToParent>, ), >; fn deref(&self) -> &Self::Target { &self.inner } } impl<'w, 's> DerefMut for AnimationQuery<'w, 's> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } impl<'w, 's> AnimationQuery<'w, 's> { /// Given an entity, ensure that the currently playing animation matches /// the provided name. This method does not change the current animation /// step pub fn assert_animation<T: ToString>(&mut self, e: Entity, name: T) -> Result<()> { let (_, _, _, mut status) = self.inner.get_mut(e)?; status.assert_animation(name); Ok(()) } /// Given an entity, start an animation from that entity's animation set /// from frame 0. If the currently playing animation is requested, it /// will be restarted pub fn start_animation<T: ToString>(&mut self, e: Entity, name: T) -> Result<()> { let (_, _, _, mut status) = self.inner.get_mut(e)?; status.start_animation(name); Ok(()) } /// Given an entity, start an animation from that entity's animation set /// from frame 0. If the currently playing animation is requested, no change /// will happen and it will continue to play uninterrupted pub fn start_or_continue_animation<T: ToString>(&mut self, e: Entity, name: T) -> Result<()> { let (_, _, _, mut status) = self.inner.get_mut(e)?; status.start_or_continue(name); Ok(()) } /// Given an entity, start playing an animation once and then switch /// to a looped version of another animation. If the currently playing /// animation is requested, it will be restarted pub fn start_animation_and_then<T: ToString, Y: ToString>( &mut self, e: Entity, name: T, next: Y, ) -> Result<()> { let (_, _, mut mode, mut status) = self.inner.get_mut(e)?; *mode = AnimationMode::OnceThenPlay(next.to_string()); status.active_name = name.to_string(); status.active_step = 0; Ok(()) } /// Given an entity, start playing an animation once and then switch /// to a looped version of another animation. If the currently playing /// animation is requested, it will be restarted pub fn continue_animation_and_then<T: ToString, Y: ToString>( &mut self, e: Entity, name: T, next: Y, ) -> Result<()> { let (_, _, mut mode, mut status) = self.inner.get_mut(e)?; let name = name.to_string(); *mode = AnimationMode::OnceThenPlay(next.to_string()); if status.active_name != name { status.active_name = name; status.active_step = 0; } Ok(()) } /// Set an entity's animations to play one time. The animation /// will pause on the last frame pub fn play_once(&mut self, e: Entity) -> Result<()> { let (_, _, mut mode, _) = self.inner.get_mut(e)?; *mode = AnimationMode::Once; Ok(()) } /// Set an entity's animations to loop continuously. The /// animation will reset to the start once it has reached the /// last frame pub fn play_loop(&mut self, e: Entity) -> Result<()> { let (_, _, mut mode, _) = self.inner.get_mut(e)?; *mode = AnimationMode::Loop; Ok(()) } /// Set an entity's animations to play one time. The animation /// will be changed to the specified set once the current animation /// has reached the last frame pub fn play_next<T: ToString>(&mut self, e: Entity, next: T) -> Result<()> { let (_, _, mut mode, _) = self.inner.get_mut(e)?; *mode = AnimationMode::OnceThenPlay(next.to_string()); Ok(()) } /// Measure the amount of time it will take an animation to run. /// The exact time that an animation runs for will differ slightly /// from this, due to the minimum time increment being 1 frame /// (16ms when running at 60fps) pub fn get_animation_duration<T: ToString>( &self, handle: Handle<AnimationSet>, name: T, ) -> Result<Duration> { let sheet = self .animations .get(&handle) .ok_or_else(|| anyhow::Error::msg("Failed to fetch animation set"))?; let details = sheet.get(name.to_string().as_str()).ok_or_else(|| { anyhow::Error::msg(format!("Missing animation from set: {}", name.to_string())) })?; Ok(Duration::from_secs_f32( details.frame_secs * details.frames.len() as f32, )) } pub fn resolve_animation( &self, handle: &Handle<AnimationSet>, name: &String, ) -> Option<&AnimationFrames> { self.animations .get(handle) .and_then(|sheet| sheet.get(name)) } /// Check whether or not an entity is currently paused. If so, /// the entity will not tick its animation state pub fn is_paused(&self, e: Entity) -> bool { self.paused.get(e).is_ok() } /// Attach a "paused" marker to this entity that will cause /// standard animations to not tick. This is safe to call /// with an entity that might already be paused; no effect will /// take place pub fn pause(&mut self, e: Entity) { self.commands.entity(e).insert(AnimationPaused); } /// Remove any "paused" markers from this entity, which will cause /// standard animations to resume ticking. This is safe to call /// with an entity that might already be paused; no effect will /// take place pub fn unpause(&mut self, e: Entity) { self.commands.entity(e).remove::<AnimationPaused>(); } pub fn set_direction(&mut self, e: Entity, horizontal: Horizontal, vertical: Vertical) { if let Ok(mut direction) = self.direction.get_mut(e) { direction.horizontal = horizontal; direction.vertical = vertical; } } pub fn set_partial_direction( &mut self, e: Entity, horizontal: Option<Horizontal>, vertical: Option<Vertical>, ) { if let Ok(mut direction) = self.direction.get_mut(e) { if let Some(horizontal) = horizontal { direction.horizontal = horizontal; } if let Some(vertical) = vertical { direction.vertical = vertical; } } } /// Apply a number of delta seconds to all unpaused animations pub fn tick_all(&mut self, dt: f32) -> Result<()> { for (entity, handle, mut mode, mut status) in &mut self.inner { if self.paused.get(entity).is_ok() { continue; } let sheet = match self.animations.get(handle) { Some(sheet) => sheet, None => continue, }; if let Ok(mut status) = self.action_animation.get_mut(entity) { let current = match sheet.get(&status.name) { Some(set) => set, None => { self.commands.entity(entity).remove::<AnimationOverride>(); continue; } }; status.frame_time += dt; while status.frame_time >= current.frame_secs { status.frame_time -= current.frame_secs; status.frame_step += 1; } if status.frame_step >= current.frames.len() { self.commands.entity(entity).remove::<AnimationOverride>(); self.events.send(AnimationCompleted { entity, user_data: status.user_data, }); status.frame_step = current.frames.len() - 1; } if let Ok(mut sprite) = self.tile_sprite.get_mut(entity) { sprite.index = current.frames[status.frame_step]; } } else { let mut current = match sheet.get(&status.active_name) { Some(set) => set, None => continue, }; status.frame_time += dt; while status.frame_time >= current.frame_secs { status.frame_time -= current.frame_secs; status.active_step += 1; } if status.active_step >= current.frames.len() { match mode.clone() { AnimationMode::Loop => { status.active_step = 0; } AnimationMode::Once => { status.active_step = current.frames.len() - 1; } AnimationMode::OnceThenPlay(next) => { *mode = AnimationMode::Loop; status.active_name = next.clone(); status.frame_time = 0.0; status.active_step = 0; current = match sheet.get(&status.active_name) { Some(set) => set, None => continue, }; } } } if let Ok(mut sprite) = self.tile_sprite.get_mut(entity) { sprite.index = current.frames[status.active_step]; } } } Ok(()) } /// Syncs all animations that are set to track their parents pub fn sync_parent_animations(&mut self) { for (entity, parent, handle, mut status) in &mut self.inner_child { if let Ok((_, _, _, parent_status)) = self.inner.get(**parent) { *status = parent_status.clone(); } if let Ok(mut sprite) = self.tile_sprite.get_mut(entity) { if let Some(current) = self .animations .get(handle) .and_then(|sheet| sheet.get(&status.active_name)) { sprite.index = current.frames[status.active_step]; } } } } pub fn apply_directionality_to<T: ToString>(&mut self, action: T, e: Entity) -> Result<()> { if self.paused.get(e).is_ok() { return Ok(()); } let (_, animations, _, mut status) = self.inner.get_mut(e)?; let direction = self.direction.get(e)?; let over = self.action_animation.get(e).ok(); let set = self .animations .get(animations) .ok_or_else(|| Error::msg("Missing Animation"))?; let fallback = over .map(|o| o.name.clone()) .unwrap_or_else(|| action.to_string()); let directed = format!("{}_{}", &fallback, direction); // log::info!("Directed - {}", directed); if set.contains_key(&directed) { status.start_or_continue(directed); } else { status.start_or_continue(fallback); } Ok(()) } }