query.rs 10.70 KiB
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(())
}
}