Path: blob/main/crates/bevy_animation/src/transition.rs
6595 views
//! Animation transitions.1//!2//! Please note that this is an unstable temporary API. It may be replaced by a3//! state machine in the future.45use bevy_ecs::{6component::Component,7reflect::ReflectComponent,8system::{Query, Res},9};10use bevy_reflect::{std_traits::ReflectDefault, Reflect};11use bevy_time::Time;12use core::time::Duration;1314use crate::{graph::AnimationNodeIndex, ActiveAnimation, AnimationPlayer};1516/// Manages fade-out of animation blend factors, allowing for smooth transitions17/// between animations.18///19/// To use this component, place it on the same entity as the20/// [`AnimationPlayer`] and [`AnimationGraphHandle`](crate::AnimationGraphHandle). It'll take21/// responsibility for adjusting the weight on the [`ActiveAnimation`] in order22/// to fade out animations smoothly.23///24/// When using an [`AnimationTransitions`] component, you should play all25/// animations through the [`AnimationTransitions::play`] method, rather than by26/// directly manipulating the [`AnimationPlayer`]. Playing animations through27/// the [`AnimationPlayer`] directly will cause the [`AnimationTransitions`]28/// component to get confused about which animation is the "main" animation, and29/// transitions will usually be incorrect as a result.30#[derive(Component, Default, Reflect)]31#[reflect(Component, Default, Clone)]32pub struct AnimationTransitions {33main_animation: Option<AnimationNodeIndex>,34transitions: Vec<AnimationTransition>,35}3637// This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`.38impl Clone for AnimationTransitions {39fn clone(&self) -> Self {40Self {41main_animation: self.main_animation,42transitions: self.transitions.clone(),43}44}4546fn clone_from(&mut self, source: &Self) {47self.main_animation = source.main_animation;48self.transitions.clone_from(&source.transitions);49}50}5152/// An animation that is being faded out as part of a transition53#[derive(Debug, Clone, Copy, Reflect)]54#[reflect(Clone)]55pub struct AnimationTransition {56/// The current weight. Starts at 1.0 and goes to 0.0 during the fade-out.57current_weight: f32,58/// How much to decrease `current_weight` per second59weight_decline_per_sec: f32,60/// The animation that is being faded out61animation: AnimationNodeIndex,62}6364impl AnimationTransitions {65/// Creates a new [`AnimationTransitions`] component, ready to be added to66/// an entity with an [`AnimationPlayer`].67pub fn new() -> AnimationTransitions {68AnimationTransitions::default()69}7071/// Plays a new animation on the given [`AnimationPlayer`], fading out any72/// existing animations that were already playing over the73/// `transition_duration`.74///75/// Pass [`Duration::ZERO`] to instantly switch to a new animation, avoiding76/// any transition.77pub fn play<'p>(78&mut self,79player: &'p mut AnimationPlayer,80new_animation: AnimationNodeIndex,81transition_duration: Duration,82) -> &'p mut ActiveAnimation {83if let Some(old_animation_index) = self.main_animation.replace(new_animation)84&& let Some(old_animation) = player.animation_mut(old_animation_index)85&& !old_animation.is_paused()86{87self.transitions.push(AnimationTransition {88current_weight: old_animation.weight,89weight_decline_per_sec: 1.0 / transition_duration.as_secs_f32(),90animation: old_animation_index,91});92}9394// If already transitioning away from this animation, cancel the transition.95// Otherwise the transition ending would incorrectly stop the new animation.96self.transitions97.retain(|transition| transition.animation != new_animation);9899player.start(new_animation)100}101102/// Obtain the currently playing main animation.103pub fn get_main_animation(&self) -> Option<AnimationNodeIndex> {104self.main_animation105}106}107108/// A system that alters the weight of currently-playing transitions based on109/// the current time and decline amount.110pub fn advance_transitions(111mut query: Query<(&mut AnimationTransitions, &mut AnimationPlayer)>,112time: Res<Time>,113) {114// We use a "greedy layer" system here. The top layer (most recent115// transition) gets as much as weight as it wants, and the remaining amount116// is divided between all the other layers, eventually culminating in the117// currently-playing animation receiving whatever's left. This results in a118// nicely normalized weight.119for (mut animation_transitions, mut player) in query.iter_mut() {120let mut remaining_weight = 1.0;121122for transition in &mut animation_transitions.transitions.iter_mut().rev() {123// Decrease weight.124transition.current_weight = (transition.current_weight125- transition.weight_decline_per_sec * time.delta_secs())126.max(0.0);127128// Update weight.129let Some(ref mut animation) = player.animation_mut(transition.animation) else {130continue;131};132animation.weight = transition.current_weight * remaining_weight;133remaining_weight -= animation.weight;134}135136if let Some(main_animation_index) = animation_transitions.main_animation137&& let Some(ref mut animation) = player.animation_mut(main_animation_index)138{139animation.weight = remaining_weight;140}141}142}143144/// A system that removed transitions that have completed from the145/// [`AnimationTransitions`] object.146pub fn expire_completed_transitions(147mut query: Query<(&mut AnimationTransitions, &mut AnimationPlayer)>,148) {149for (mut animation_transitions, mut player) in query.iter_mut() {150animation_transitions.transitions.retain(|transition| {151let expire = transition.current_weight <= 0.0;152if expire {153player.stop(transition.animation);154}155!expire156});157}158}159160161