//! 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::{AddAsset, Assets, Handle}; use bevy_core::Name; use bevy_ecs::prelude::*; 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); } } } #[derive(Reflect)] struct PlayingAnimation { repeat: bool, speed: f32, elapsed: f32, animation_clip: Handle, path_cache: Vec>>, } impl Default for PlayingAnimation { fn default() -> Self { Self { repeat: false, speed: 1.0, elapsed: 0.0, animation_clip: Default::default(), path_cache: Vec::new(), } } } /// 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.animation.animation_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.animation.animation_clip != handle || self.is_paused() { self.start_with_transition(handle, transition_duration); } self } /// Set the animation to repeat pub fn repeat(&mut self) -> &mut Self { self.animation.repeat = true; self } /// Stop the animation from repeating pub fn stop_repeating(&mut self) -> &mut Self { self.animation.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.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 to a specific time in the animation pub fn set_elapsed(&mut self, elapsed: f32) -> &mut Self { self.animation.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