From 17f630c360f07d1567662272c70786daee1aef81 Mon Sep 17 00:00:00 2001 From: Sirmadeira Date: Sat, 26 Apr 2025 20:32:15 -0300 Subject: [PATCH 01/25] Making so animation transition is non reliant on animation players --- crates/bevy_animation/src/transition.rs | 101 ++++++------------------ 1 file changed, 22 insertions(+), 79 deletions(-) diff --git a/crates/bevy_animation/src/transition.rs b/crates/bevy_animation/src/transition.rs index 494855970441d..1148fad4319fb 100644 --- a/crates/bevy_animation/src/transition.rs +++ b/crates/bevy_animation/src/transition.rs @@ -3,6 +3,7 @@ //! Please note that this is an unstable temporary API. It may be replaced by a //! state machine in the future. +use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, reflect::ReflectComponent, @@ -12,7 +13,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_time::Time; use core::time::Duration; -use crate::{graph::AnimationNodeIndex, ActiveAnimation, AnimationPlayer}; +use crate::{graph::{AnimationGraph, AnimationGraphNode, AnimationNodeIndex}, AnimationPlayer}; /// Manages fade-out of animation blend factors, allowing for smooth transitions /// between animations. @@ -21,89 +22,45 @@ use crate::{graph::AnimationNodeIndex, ActiveAnimation, AnimationPlayer}; /// [`AnimationPlayer`] and [`AnimationGraphHandle`](crate::AnimationGraphHandle). 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)] +#[derive(Component, Default, Reflect,Deref,DerefMut,Clone)] #[reflect(Component, Default, Clone)] -pub struct AnimationTransitions { - main_animation: Option, - transitions: Vec, -} +pub struct AnimationTransitions (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)] +#[derive(Debug, Clone, Reflect)] #[reflect(Clone)] 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, + /// The animation that is beind fade out + old_node:AnimationGraphNode, + /// The animation that is gaining weight + new_node: AnimationGraphNode, } 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>( + pub fn transition( &mut self, - player: &'p mut AnimationPlayer, + graph: & mut AnimationGraph, + old_animation: AnimationNodeIndex, 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, - }); - } - } - } + ) { + let old_node = graph.get(old_animation).unwrap().clone(); + let new_node = graph.get(new_animation).unwrap().clone(); - // 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); + self.push(AnimationTransition { current_weight: old_node.weight, weight_decline_per_sec: 1.0 / transition_duration.as_secs_f32(), old_node,new_node }); - player.start(new_animation) - } - /// Obtain the currently playing main animation. - pub fn get_main_animation(&self) -> Option { - self.main_animation } } @@ -121,24 +78,14 @@ pub fn advance_transitions( for (mut animation_transitions, mut player) in query.iter_mut() { let mut remaining_weight = 1.0; - for transition in &mut animation_transitions.transitions.iter_mut().rev() { + for transition in &mut animation_transitions.iter_mut().rev() { // Decrease weight. transition.current_weight = (transition.current_weight - transition.weight_decline_per_sec * time.delta_secs()) .max(0.0); - // Update weight. - let Some(ref mut animation) = player.animation_mut(transition.animation) else { - continue; - }; - animation.weight = transition.current_weight * remaining_weight; - remaining_weight -= animation.weight; - } - - if let Some(main_animation_index) = animation_transitions.main_animation { - if let Some(ref mut animation) = player.animation_mut(main_animation_index) { - animation.weight = remaining_weight; - } + transition.old_node.weight = transition.current_weight * remaining_weight; + remaining_weight -= transition.old_node.weight; } } } @@ -148,13 +95,9 @@ pub fn advance_transitions( pub fn expire_completed_transitions( mut query: Query<(&mut AnimationTransitions, &mut AnimationPlayer)>, ) { - for (mut animation_transitions, mut player) in query.iter_mut() { - animation_transitions.transitions.retain(|transition| { - let expire = transition.current_weight <= 0.0; - if expire { - player.stop(transition.animation); - } - !expire + for (mut animation_transitions, _player) in query.iter_mut() { + animation_transitions.retain(|transition| { + transition.current_weight > 0.0 }); } } From 0ff35438c4229cc836559cf3e8623ac34144e166 Mon Sep 17 00:00:00 2001 From: Sirmadeira Date: Sat, 26 Apr 2025 20:33:21 -0300 Subject: [PATCH 02/25] Format --- crates/bevy_animation/src/transition.rs | 29 ++++++++++++++----------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/crates/bevy_animation/src/transition.rs b/crates/bevy_animation/src/transition.rs index 1148fad4319fb..976aac27f73c8 100644 --- a/crates/bevy_animation/src/transition.rs +++ b/crates/bevy_animation/src/transition.rs @@ -13,7 +13,10 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_time::Time; use core::time::Duration; -use crate::{graph::{AnimationGraph, AnimationGraphNode, AnimationNodeIndex}, AnimationPlayer}; +use crate::{ + graph::{AnimationGraph, AnimationGraphNode, AnimationNodeIndex}, + AnimationPlayer, +}; /// Manages fade-out of animation blend factors, allowing for smooth transitions /// between animations. @@ -22,13 +25,12 @@ use crate::{graph::{AnimationGraph, AnimationGraphNode, AnimationNodeIndex}, An /// [`AnimationPlayer`] and [`AnimationGraphHandle`](crate::AnimationGraphHandle). It'll take /// responsibility for adjusting the weight on the [`ActiveAnimation`] in order /// to fade out animations smoothly. -#[derive(Component, Default, Reflect,Deref,DerefMut,Clone)] +#[derive(Component, Default, Reflect, Deref, DerefMut, Clone)] #[reflect(Component, Default, Clone)] -pub struct AnimationTransitions (Vec); - +pub struct AnimationTransitions(Vec); /// An animation that is being faded out as part of a transition -#[derive(Debug, Clone, Reflect)] +#[derive(Debug, Clone, Reflect)] #[reflect(Clone)] pub struct AnimationTransition { /// The current weight. Starts at 1.0 and goes to 0.0 during the fade-out. @@ -36,7 +38,7 @@ pub struct AnimationTransition { /// How much to decrease `current_weight` per second weight_decline_per_sec: f32, /// The animation that is beind fade out - old_node:AnimationGraphNode, + old_node: AnimationGraphNode, /// The animation that is gaining weight new_node: AnimationGraphNode, } @@ -50,7 +52,7 @@ impl AnimationTransitions { /// any transition. pub fn transition( &mut self, - graph: & mut AnimationGraph, + graph: &mut AnimationGraph, old_animation: AnimationNodeIndex, new_animation: AnimationNodeIndex, transition_duration: Duration, @@ -58,9 +60,12 @@ impl AnimationTransitions { let old_node = graph.get(old_animation).unwrap().clone(); let new_node = graph.get(new_animation).unwrap().clone(); - self.push(AnimationTransition { current_weight: old_node.weight, weight_decline_per_sec: 1.0 / transition_duration.as_secs_f32(), old_node,new_node }); - - + self.push(AnimationTransition { + current_weight: old_node.weight, + weight_decline_per_sec: 1.0 / transition_duration.as_secs_f32(), + old_node, + new_node, + }); } } @@ -96,8 +101,6 @@ pub fn expire_completed_transitions( mut query: Query<(&mut AnimationTransitions, &mut AnimationPlayer)>, ) { for (mut animation_transitions, _player) in query.iter_mut() { - animation_transitions.retain(|transition| { - transition.current_weight > 0.0 - }); + animation_transitions.retain(|transition| transition.current_weight > 0.0); } } From debc131360e5b2fb87918938619d7f3f57b72cf9 Mon Sep 17 00:00:00 2001 From: Sirmadeira Date: Mon, 28 Apr 2025 08:52:16 -0300 Subject: [PATCH 03/25] Chore: Node transitions --- crates/bevy_animation/src/lib.rs | 90 +++++++-------- crates/bevy_animation/src/transition.rs | 146 +++++++++++++----------- 2 files changed, 122 insertions(+), 114 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 43ea343aa311c..210c6ac834b1a 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -24,7 +24,7 @@ use core::{ iter, slice, }; use graph::AnimationNodeType; -use prelude::AnimationCurveEvaluator; +use prelude::{handle_node_transition, AnimationCurveEvaluator}; use crate::{ graph::{AnimationGraphHandle, ThreadedAnimationGraphs}, @@ -60,10 +60,53 @@ pub mod prelude { use crate::{ animation_curves::AnimationCurve, graph::{AnimationGraph, AnimationGraphAssetLoader, AnimationNodeIndex}, - transition::{advance_transitions, expire_completed_transitions, AnimationTransitions}, + transition::{expire_completed_transitions, AnimationTransitions}, }; use alloc::sync::Arc; +/// Adds animation support to an app +#[derive(Default)] +pub struct AnimationPlugin; + +impl Plugin for AnimationPlugin { + fn build(&self, app: &mut App) { + app.init_asset::() + .init_asset::() + .init_asset_loader::() + .register_asset_reflect::() + .register_asset_reflect::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .init_resource::() + .add_systems( + PostUpdate, + ( + graph::thread_animation_graphs.before(AssetEvents), + handle_node_transition, + advance_animations, + // TODO: `animate_targets` can animate anything, so + // ambiguity testing currently considers it ambiguous with + // every other system in `PostUpdate`. We may want to move + // it to its own system set after `Update` but before + // `PostUpdate`. For now, we just disable ambiguity testing + // for this system. + animate_targets + .before(bevy_render::mesh::inherit_weights) + .ambiguous_with_all(), + trigger_untargeted_animation_events, + expire_completed_transitions, + ) + .chain() + .in_set(Animation) + .before(TransformSystem::TransformPropagate), + ); + } +} + /// The [UUID namespace] of animation targets (e.g. bones). /// /// [UUID namespace]: https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based) @@ -1223,49 +1266,6 @@ pub fn animate_targets( }); } -/// Adds animation support to an app -#[derive(Default)] -pub struct AnimationPlugin; - -impl Plugin for AnimationPlugin { - fn build(&self, app: &mut App) { - app.init_asset::() - .init_asset::() - .init_asset_loader::() - .register_asset_reflect::() - .register_asset_reflect::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .init_resource::() - .add_systems( - PostUpdate, - ( - graph::thread_animation_graphs.before(AssetEvents), - advance_transitions, - advance_animations, - // TODO: `animate_targets` can animate anything, so - // ambiguity testing currently considers it ambiguous with - // every other system in `PostUpdate`. We may want to move - // it to its own system set after `Update` but before - // `PostUpdate`. For now, we just disable ambiguity testing - // for this system. - animate_targets - .before(bevy_render::mesh::inherit_weights) - .ambiguous_with_all(), - trigger_untargeted_animation_events, - expire_completed_transitions, - ) - .chain() - .in_set(Animation) - .before(TransformSystem::TransformPropagate), - ); - } -} - impl AnimationTargetId { /// Creates a new [`AnimationTargetId`] by hashing a list of names. /// diff --git a/crates/bevy_animation/src/transition.rs b/crates/bevy_animation/src/transition.rs index 976aac27f73c8..d990d25d17053 100644 --- a/crates/bevy_animation/src/transition.rs +++ b/crates/bevy_animation/src/transition.rs @@ -1,106 +1,114 @@ -//! Animation transitions. -//! -//! Please note that this is an unstable temporary API. It may be replaced by a -//! state machine in the future. - +use bevy_asset::{Assets, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, reflect::ReflectComponent, - system::{Query, Res}, + system::{Query, Res, ResMut}, }; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_time::Time; use core::time::Duration; -use crate::{ - graph::{AnimationGraph, AnimationGraphNode, AnimationNodeIndex}, - AnimationPlayer, -}; +use crate::graph::{AnimationGraph, AnimationNodeIndex}; -/// 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 [`AnimationGraphHandle`](crate::AnimationGraphHandle). It'll take -/// responsibility for adjusting the weight on the [`ActiveAnimation`] in order -/// to fade out animations smoothly. -#[derive(Component, Default, Reflect, Deref, DerefMut, Clone)] -#[reflect(Component, Default, Clone)] +/// Component responsible for making transitions among two given nodes/states. +/// He is also capable of making +#[derive(Component, Default, Reflect, Deref, DerefMut)] +#[reflect(Component, Default)] pub struct AnimationTransitions(Vec); /// An animation that is being faded out as part of a transition -#[derive(Debug, Clone, Reflect)] -#[reflect(Clone)] +#[derive(Debug, 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 beind fade out - old_node: AnimationGraphNode, - /// The animation that is gaining weight - new_node: AnimationGraphNode, + old_node: AnimationNodeIndex, + new_node: AnimationNodeIndex, + graph: Handle, + weight: f32, } - impl AnimationTransitions { - /// 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 transition( + /// Transition between one graph node to another according to the given duration + pub fn transition_nodes( &mut self, - graph: &mut AnimationGraph, - old_animation: AnimationNodeIndex, - new_animation: AnimationNodeIndex, - transition_duration: Duration, + graph: Handle, + old_node: AnimationNodeIndex, + new_node: AnimationNodeIndex, + transition: Duration, ) { - let old_node = graph.get(old_animation).unwrap().clone(); - let new_node = graph.get(new_animation).unwrap().clone(); - self.push(AnimationTransition { - current_weight: old_node.weight, - weight_decline_per_sec: 1.0 / transition_duration.as_secs_f32(), + weight_decline_per_sec: 1.0 / transition.as_secs_f32(), old_node, new_node, + graph, + weight: 1.0, }); } + + // /// 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) + // } } -/// 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)>, +/// System responsible for handling [`AnimationTransitions`] transitioning nodes among each other. According to the pacing defined by user. +pub fn handle_node_transition( + mut query: Query<&mut AnimationTransitions>, + mut assets_graph: ResMut>, time: Res