//! Animation for the game engine Bevy #![warn(missing_docs)] #![allow(clippy::type_complexity)] use std::ops::Deref; use std::time::Duration; use bevy_app::{App, Plugin, PostUpdate}; use bevy_asset::{Asset, AssetApp, Assets, Handle}; use bevy_core::Name; use bevy_ecs::prelude::*; use bevy_hierarchy::{Children, Parent}; use bevy_math::{Quat, Vec3}; use bevy_reflect::Reflect; use bevy_render::mesh::morph::MorphWeights; 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, Clone, Debug)] pub enum Keyframes { /// Keyframes for rotation. Rotation(Vec), /// Keyframes for translation. Translation(Vec), /// Keyframes for scale. Scale(Vec), /// Keyframes for morph target weights. /// /// Note that in `.0`, each contiguous `target_count` values is a single /// keyframe representing the weight values at given keyframe. /// /// This follows the [glTF design]. /// /// [glTF design]: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#animations Weights(Vec), } /// Describes how an attribute of a [`Transform`] or [`MorphWeights`] should be animated. /// /// `keyframe_timestamps` and `keyframes` should have the same length. #[derive(Reflect, 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, 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(Asset, Reflect, Clone, Debug, Default)] 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); } } /// Whether this animation clip can run on entity with given [`Name`]. pub fn compatible_with(&self, name: &Name) -> bool { self.paths.keys().any(|path| &path.parts[0] == name) } } /// Repetition behavior of an animation. #[derive(Reflect, Copy, Clone, Default)] pub enum RepeatAnimation { /// The animation will finish after running once. #[default] Never, /// The animation will finish after running "n" times. Count(u32), /// The animation will never finish. Forever, } #[derive(Reflect)] struct PlayingAnimation { repeat: RepeatAnimation, speed: f32, /// Total time the animation has been played. /// /// Note: Time does not increase when the animation is paused or after it has completed. elapsed: f32, /// The timestamp inside of the animation clip. /// /// Note: This will always be in the range [0.0, animation clip duration] seek_time: f32, animation_clip: Handle, path_cache: Vec>>, /// Number of times the animation has completed. /// If the animation is playing in reverse, this increments when the animation passes the start. completions: u32, } impl Default for PlayingAnimation { fn default() -> Self { Self { repeat: RepeatAnimation::default(), speed: 1.0, elapsed: 0.0, seek_time: 0.0, animation_clip: Default::default(), path_cache: Vec::new(), completions: 0, } } } impl PlayingAnimation { /// Check if the animation has finished, based on its repetition behavior and the number of times it has repeated. /// /// Note: An animation with `RepeatAnimation::Forever` will never finish. #[inline] pub fn is_finished(&self) -> bool { match self.repeat { RepeatAnimation::Forever => false, RepeatAnimation::Never => self.completions >= 1, RepeatAnimation::Count(n) => self.completions >= n, } } /// Update the animation given the delta time and the duration of the clip being played. #[inline] fn update(&mut self, delta: f32, clip_duration: f32) { if self.is_finished() { return; } self.elapsed += delta; self.seek_time += delta * self.speed; let over_time = self.speed > 0.0 && self.seek_time >= clip_duration; let under_time = self.speed < 0.0 && self.seek_time < 0.0; if over_time || under_time { self.completions += 1; if self.is_finished() { return; } } if self.seek_time >= clip_duration { self.seek_time %= clip_duration; } // Note: assumes delta is never lower than -clip_duration if self.seek_time < 0.0 { self.seek_time += clip_duration; } } /// Reset back to the initial state as if no time has elapsed. fn replay(&mut self) { self.completions = 0; self.elapsed = 0.0; self.seek_time = 0.0; } } /// An animation that is being faded out as part of a transition struct AnimationTransition { /// The current weight. Starts at 1.0 and goes to 0.0 during the fade-out. current_weight: f32, /// How much to decrease `current_weight` per second weight_decline_per_sec: f32, /// The animation that is being faded out animation: PlayingAnimation, } /// Animation controls #[derive(Component, Default, Reflect)] #[reflect(Component)] pub struct AnimationPlayer { paused: bool, animation: PlayingAnimation, // List of previous animations we're currently transitioning away from. // Usually this is empty, when transitioning between animations, there is // one entry. When another animation transition happens while a transition // is still ongoing, then there can be more than one entry. // Once a transition is finished, it will be automatically removed from the list #[reflect(ignore)] transitions: Vec, } impl AnimationPlayer { /// Start playing an animation, resetting state of the player. /// This will use a linear blending between the previous and the new animation to make a smooth transition. pub fn start(&mut self, handle: Handle) -> &mut Self { self.animation = PlayingAnimation { animation_clip: handle, ..Default::default() }; // We want a hard transition. // In case any previous transitions are still playing, stop them self.transitions.clear(); self } /// Start playing an animation, resetting state of the player. /// This will use a linear blending between the previous and the new animation to make a smooth transition. pub fn start_with_transition( &mut self, handle: Handle, transition_duration: Duration, ) -> &mut Self { let mut animation = PlayingAnimation { animation_clip: handle, ..Default::default() }; std::mem::swap(&mut animation, &mut self.animation); // Add the current transition. If other transitions are still ongoing, // this will keep those transitions running and cause a transition between // the output of that previous transition to the new animation. self.transitions.push(AnimationTransition { current_weight: 1.0, weight_decline_per_sec: 1.0 / transition_duration.as_secs_f32(), animation, }); self } /// Start playing an animation, resetting state of the player, unless the requested animation is already playing. /// If `transition_duration` is set, this will use a linear blending /// between the previous and the new animation to make a smooth transition pub fn play(&mut self, handle: Handle) -> &mut Self { if !self.is_playing_clip(&handle) || self.is_paused() { self.start(handle); } self } /// Start playing an animation, resetting state of the player, unless the requested animation is already playing. /// This will use a linear blending between the previous and the new animation to make a smooth transition pub fn play_with_transition( &mut self, handle: Handle, transition_duration: Duration, ) -> &mut Self { if !self.is_playing_clip(&handle) || self.is_paused() { self.start_with_transition(handle, transition_duration); } self } /// Handle to the animation clip being played. pub fn animation_clip(&self) -> &Handle { &self.animation.animation_clip } /// Check if the given animation clip is being played. pub fn is_playing_clip(&self, handle: &Handle) -> bool { self.animation_clip() == handle } /// Check if the playing animation has finished, according to the repetition behavior. pub fn is_finished(&self) -> bool { self.animation.is_finished() } /// Sets repeat to [`RepeatAnimation::Forever`]. /// /// See also [`Self::set_repeat`]. pub fn repeat(&mut self) -> &mut Self { self.animation.repeat = RepeatAnimation::Forever; self } /// Set the repetition behaviour of the animation. pub fn set_repeat(&mut self, repeat: RepeatAnimation) -> &mut Self { self.animation.repeat = repeat; self } /// Repetition behavior of the animation. pub fn repeat_mode(&self) -> RepeatAnimation { self.animation.repeat } /// Number of times the animation has completed. pub fn completions(&self) -> u32 { self.animation.completions } /// Check if the animation is playing in reverse. pub fn is_playback_reversed(&self) -> bool { self.animation.speed < 0.0 } /// 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.animation.speed } /// Set the speed of the animation playback pub fn set_speed(&mut self, speed: f32) -> &mut Self { self.animation.speed = speed; self } /// Time elapsed playing the animation pub fn elapsed(&self) -> f32 { self.animation.elapsed } /// Seek time inside of the animation. Always within the range [0.0, clip duration]. pub fn seek_time(&self) -> f32 { self.animation.seek_time } /// Seek to a specific time in the animation. pub fn seek_to(&mut self, seek_time: f32) -> &mut Self { self.animation.seek_time = seek_time; self } /// Reset the animation to its initial state, as if no time has elapsed. pub fn replay(&mut self) { self.animation.replay(); } } fn entity_from_path( 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); let mut parts = path.parts.iter().enumerate(); // check the first name is the root node which we already have let Some((_, root_name)) = parts.next() else { return None; }; if names.get(current_entity) != Ok(root_name) { return None; } for (idx, part) in parts { 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<(Has, Option<&Parent>)>, ) -> bool { let Some(mut current) = player_parent.map(Parent::get) else { return true; }; loop { let Ok((has_player, parent)) = parents.get(current) else { return true; }; if has_player { 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 #[allow(clippy::too_many_arguments)] pub fn animation_player( time: Res