//! Animation transitions. //! //! Please note that this is an unstable temporary API. It may be replaced by a //! state machine in the future. use bevy_ecs::{ component::Component, reflect::ReflectComponent, system::{Query, Res}, }; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_time::Time; use bevy_utils::Duration; use crate::{graph::AnimationNodeIndex, ActiveAnimation, AnimationPlayer}; /// Manages fade-out of animation blend factors, allowing for smooth transitions /// between animations. /// /// To use this component, place it on the same entity as the /// [`AnimationPlayer`] and [`bevy_asset::Handle`]. It'll take /// responsibility for adjusting the weight on the [`ActiveAnimation`] in order /// to fade out animations smoothly. /// /// When using an [`AnimationTransitions`] component, you should play all /// animations through the [`AnimationTransitions::play`] method, rather than by /// directly manipulating the [`AnimationPlayer`]. Playing animations through /// the [`AnimationPlayer`] directly will cause the [`AnimationTransitions`] /// component to get confused about which animation is the "main" animation, and /// transitions will usually be incorrect as a result. #[derive(Component, Default, Reflect)] #[reflect(Component, Default)] pub struct AnimationTransitions { main_animation: Option, transitions: Vec, } // This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`. impl Clone for AnimationTransitions { fn clone(&self) -> Self { Self { main_animation: self.main_animation, transitions: self.transitions.clone(), } } fn clone_from(&mut self, source: &Self) { self.main_animation = source.main_animation; self.transitions.clone_from(&source.transitions); } } /// An animation that is being faded out as part of a transition #[derive(Debug, Clone, Copy, Reflect)] pub 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: AnimationNodeIndex, } impl AnimationTransitions { /// Creates a new [`AnimationTransitions`] component, ready to be added to /// an entity with an [`AnimationPlayer`]. pub fn new() -> AnimationTransitions { AnimationTransitions::default() } /// Plays a new animation on the given [`AnimationPlayer`], fading out any /// existing animations that were already playing over the /// `transition_duration`. /// /// Pass [`Duration::ZERO`] to instantly switch to a new animation, avoiding /// any transition. pub fn play<'p>( &mut self, player: &'p mut AnimationPlayer, new_animation: AnimationNodeIndex, transition_duration: Duration, ) -> &'p mut ActiveAnimation { if let Some(old_animation_index) = self.main_animation.replace(new_animation) { if let Some(old_animation) = player.animation_mut(old_animation_index) { if !old_animation.is_paused() { self.transitions.push(AnimationTransition { current_weight: old_animation.weight, weight_decline_per_sec: 1.0 / transition_duration.as_secs_f32(), animation: old_animation_index, }); } } } // If already transitioning away from this animation, cancel the transition. // Otherwise the transition ending would incorrectly stop the new animation. self.transitions .retain(|transition| transition.animation != new_animation); player.start(new_animation) } /// Obtain the currently playing main animation. pub fn get_main_animation(&self) -> Option { self.main_animation } } /// A system that alters the weight of currently-playing transitions based on /// the current time and decline amount. pub fn advance_transitions( mut query: Query<(&mut AnimationTransitions, &mut AnimationPlayer)>, time: Res