//! Animation for the game engine Bevy #![warn(missing_docs)] use std::ops::Deref; use bevy_app::{App, CoreStage, Plugin}; use bevy_asset::{AddAsset, Assets, Handle}; use bevy_core::Name; use bevy_ecs::{ change_detection::DetectChanges, entity::Entity, prelude::Component, query::With, reflect::ReflectComponent, schedule::IntoSystemDescriptor, system::{Query, Res}, }; use bevy_hierarchy::{Children, Parent}; use bevy_math::{Quat, Vec3}; use bevy_reflect::{FromReflect, Reflect, TypeUuid}; use bevy_time::Time; use bevy_transform::{prelude::Transform, TransformSystem}; use bevy_utils::{tracing::warn, HashMap}; #[allow(missing_docs)] pub mod prelude { #[doc(hidden)] pub use crate::{ AnimationClip, AnimationPlayer, AnimationPlugin, EntityPath, Keyframes, VariableCurve, }; } /// List of keyframes for one of the attribute of a [`Transform`]. #[derive(Reflect, FromReflect, Clone, Debug)] pub enum Keyframes { /// Keyframes for rotation. Rotation(Vec), /// Keyframes for translation. Translation(Vec), /// Keyframes for scale. Scale(Vec), } /// Describes how an attribute of a [`Transform`] should be animated. /// /// `keyframe_timestamps` and `keyframes` should have the same length. #[derive(Reflect, FromReflect, Clone, Debug)] pub struct VariableCurve { /// Timestamp for each of the keyframes. pub keyframe_timestamps: Vec, /// List of the keyframes. pub keyframes: Keyframes, } /// Path to an entity, with [`Name`]s. Each entity in a path must have a name. #[derive(Reflect, FromReflect, Clone, Debug, Hash, PartialEq, Eq, Default)] pub struct EntityPath { /// Parts of the path pub parts: Vec, } /// A list of [`VariableCurve`], and the [`EntityPath`] to which they apply. #[derive(Reflect, FromReflect, Clone, TypeUuid, Debug, Default)] #[uuid = "d81b7179-0448-4eb0-89fe-c067222725bf"] pub struct AnimationClip { curves: Vec>, paths: HashMap, duration: f32, } impl AnimationClip { #[inline] /// [`VariableCurve`]s for each bone. Indexed by the bone ID. pub fn curves(&self) -> &Vec> { &self.curves } /// Gets the curves for a bone. /// /// Returns `None` if the bone is invalid. #[inline] pub fn get_curves(&self, bone_id: usize) -> Option<&'_ Vec> { self.curves.get(bone_id) } /// Gets the curves by it's [`EntityPath`]. /// /// Returns `None` if the bone is invalid. #[inline] pub fn get_curves_by_path(&self, path: &EntityPath) -> Option<&'_ Vec> { self.paths.get(path).and_then(|id| self.curves.get(*id)) } /// Duration of the clip, represented in seconds #[inline] pub fn duration(&self) -> f32 { self.duration } /// Add a [`VariableCurve`] to an [`EntityPath`]. pub fn add_curve_to_path(&mut self, path: EntityPath, curve: VariableCurve) { // Update the duration of the animation by this curve duration if it's longer self.duration = self .duration .max(*curve.keyframe_timestamps.last().unwrap_or(&0.0)); if let Some(bone_id) = self.paths.get(&path) { self.curves[*bone_id].push(curve); } else { let idx = self.curves.len(); self.curves.push(vec![curve]); self.paths.insert(path, idx); } } } /// Animation controls #[derive(Component, Reflect)] #[reflect(Component)] pub struct AnimationPlayer { paused: bool, repeat: bool, speed: f32, elapsed: f32, animation_clip: Handle, path_cache: Vec>>, } impl Default for AnimationPlayer { fn default() -> Self { Self { paused: false, repeat: false, speed: 1.0, elapsed: 0.0, animation_clip: Default::default(), path_cache: Vec::new(), } } } impl AnimationPlayer { /// Start playing an animation, resetting state of the player pub fn start(&mut self, handle: Handle) -> &mut Self { *self = Self { animation_clip: handle, ..Default::default() }; self } /// Start playing an animation, resetting state of the player, unless the requested animation is already playing. pub fn play(&mut self, handle: Handle) -> &mut Self { if self.animation_clip != handle || self.is_paused() { self.start(handle); } self } /// Set the animation to repeat pub fn repeat(&mut self) -> &mut Self { self.repeat = true; self } /// Stop the animation from repeating pub fn stop_repeating(&mut self) -> &mut Self { self.repeat = false; self } /// Pause the animation pub fn pause(&mut self) { self.paused = true; } /// Unpause the animation pub fn resume(&mut self) { self.paused = false; } /// Is the animation paused pub fn is_paused(&self) -> bool { self.paused } /// Speed of the animation playback pub fn speed(&self) -> f32 { self.speed } /// Set the speed of the animation playback pub fn set_speed(&mut self, speed: f32) -> &mut Self { self.speed = speed; self } /// Time elapsed playing the animation pub fn elapsed(&self) -> f32 { self.elapsed } /// Seek to a specific time in the animation pub fn set_elapsed(&mut self, elapsed: f32) -> &mut Self { self.elapsed = elapsed; self } } fn find_bone( root: Entity, path: &EntityPath, children: &Query<&Children>, names: &Query<&Name>, path_cache: &mut Vec>, ) -> Option { // PERF: finding the target entity can be optimised let mut current_entity = root; path_cache.resize(path.parts.len(), None); // Ignore the first name, it is the root node which we already have for (idx, part) in path.parts.iter().enumerate().skip(1) { let mut found = false; let children = children.get(current_entity).ok()?; if let Some(cached) = path_cache[idx] { if children.contains(&cached) { if let Ok(name) = names.get(cached) { if name == part { current_entity = cached; found = true; } } } } if !found { for child in children.deref() { if let Ok(name) = names.get(*child) { if name == part { // Found a children with the right name, continue to the next part current_entity = *child; path_cache[idx] = Some(*child); found = true; break; } } } } if !found { warn!("Entity not found for path {:?} on part {:?}", path, part); return None; } } Some(current_entity) } /// Verify that there are no ancestors of a given entity that have an `AnimationPlayer`. fn verify_no_ancestor_player( player_parent: Option<&Parent>, parents: &Query<(Option>, Option<&Parent>)>, ) -> bool { let Some(mut current) = player_parent.map(Parent::get) else { return true }; loop { let Ok((maybe_player, parent)) = parents.get(current) else { return true }; if maybe_player.is_some() { return false; } if let Some(parent) = parent { current = parent.get(); } else { return true; } } } /// System that will play all animations, using any entity with a [`AnimationPlayer`] /// and a [`Handle`] as an animation root pub fn animation_player( time: Res