diff --git a/crates/bevy_animation/src/graph.rs b/crates/bevy_animation/src/graph.rs index a5f4041ac780c..682bff45ecfc6 100644 --- a/crates/bevy_animation/src/graph.rs +++ b/crates/bevy_animation/src/graph.rs @@ -82,6 +82,7 @@ use crate::{AnimationClip, AnimationTargetId}; /// mask corresponds to a *mask group*, which is a set of animation targets /// (bones). An animation target can belong to any number of mask groups within /// the context of an animation graph. +/// Note - Avoid using 0 as a *mask* as that is the default mask for non masked nodes. /// /// When the appropriate bit is set in a node's mask, neither the node nor its /// descendants will animate any animation targets belonging to that mask group. diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index ae7ce42ed67a2..e1dfc8e2f95a6 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(AssetEventSystems), + 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(AnimationSystems) + .before(TransformSystems::Propagate), + ); + } +} + /// 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(AssetEventSystems), - 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(AnimationSystems) - .before(TransformSystems::Propagate), - ); - } -} - 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 494855970441d..bd1ab8dc30999 100644 --- a/crates/bevy_animation/src/transition.rs +++ b/crates/bevy_animation/src/transition.rs @@ -1,8 +1,11 @@ -//! Animation transitions. -//! -//! Please note that this is an unstable temporary API. It may be replaced by a -//! state machine in the future. +//! Animation Transitioning logic goes here! +//! This struct should in the later run be responsible for handling multi-state Animation Graph nodes. +use crate::{ + graph::{AnimationGraphHandle, AnimationNodeIndex}, + ActiveAnimation, AnimationPlayer, +}; +use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, reflect::ReflectComponent, @@ -10,151 +13,181 @@ use bevy_ecs::{ }; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_time::Time; -use core::time::Duration; - -use crate::{graph::AnimationNodeIndex, ActiveAnimation, AnimationPlayer}; +use core::{f32, time::Duration}; -/// Manages fade-out of animation blend factors, allowing for smooth transitions -/// between animations. +/// Component responsible for managing transitions between multiple nodes or states. +/// +/// It supports multiple independent "flows", where each flow represents a distinct active +/// animation or state machine. A flow tracks the transition between two states over time. /// -/// 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. +/// In the simplest case, `flow_amount` should be set to `1`, indicating a single flow. +/// However, if multiple state machines or simultaneous animations are needed, `flow_amount` +/// should be increased accordingly. /// -/// 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, Clone)] +/// Is worth mentioning, that when using `AnimationTransitions`, you should avoid messing around with player! +/// As he will do the heavy lifting for you! All you need to worry about is transitioning your flows! +/// If you do play an additional animation directly via player this WILL BREAK! +/// +/// It is also the user's responsibility to track which flow they are currently operating on +/// when triggering transitions. +/// Ex: Flow 0 - Plays idle,walks and so on. Affect whole body +/// Flow 1 - Plays close hands - Affects only hand bones. +#[derive(Component, Default, Reflect, Deref, DerefMut)] +#[reflect(Component, Default)] +#[require(AnimationGraphHandle, AnimationPlayer)] pub struct AnimationTransitions { - main_animation: Option, + #[deref] transitions: Vec, + /// Flows represent sequences of animation states. + /// For example, in cases such as masked or additive animation scenarios, a user can easily define transitions between previous and new states. + /// This concept is similar to "main" animations, but instead of one sole `ActiveAnimation`, we might have multiple active animation being controlled! + flows: Vec>, } -// This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`. -impl Clone for AnimationTransitions { - fn clone(&self) -> Self { +/// An animation node that is being faded out as part of a transition, note this controls the animations being played! +#[derive(Debug, Reflect, Clone)] +pub struct AnimationTransition { + /// How much weight we will decrease according to the given user value + duration: Duration, + /// Node to transition from + old_node: AnimationNodeIndex, + /// Node to transition into + new_node: AnimationNodeIndex, + /// Acts similarly to a local variable, tracks how far into the transition are we, should start from 1. and go to 0 + weight: f32, +} +impl AnimationTransitions { + /// Initializes the [`AnimationTransitions`] component, with ONE SINGLE flow meaning. It is expected to play only one animation at once + pub fn new() -> Self { Self { - main_animation: self.main_animation, - transitions: self.transitions.clone(), + flows: vec![None; 1_usize], + transitions: Vec::new(), } } - 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)] -#[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, -} - -impl AnimationTransitions { - /// Creates a new [`AnimationTransitions`] component, ready to be added to - /// an entity with an [`AnimationPlayer`]. - pub fn new() -> AnimationTransitions { - AnimationTransitions::default() + /// Define your flow amount and initializes your component, renember `flow_amount` is the amount of animation you want to be playing at once + pub fn new_with_flow(flow_amount: usize) -> Self { + Self { + flows: vec![None; flow_amount], + // Default transitions are instantaniously cleared + transitions: Vec::new(), + } } - /// Plays a new animation on the given [`AnimationPlayer`], fading out any - /// existing animations that were already playing over the - /// `transition_duration`. + /// Transitions the specified flow from its current node to a new node over a given duration. /// - /// Pass [`Duration::ZERO`] to instantly switch to a new animation, avoiding - /// any transition. - pub fn play<'p>( + /// This method manages transitions within one single flow. + pub fn transition<'p>( &mut self, player: &'p mut AnimationPlayer, - new_animation: AnimationNodeIndex, - transition_duration: Duration, + new_node: AnimationNodeIndex, + 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 let Some(old_node) = self.flows.get_mut(0) { + // If is first time playing, just say old node equals new node + let previous_node = old_node.unwrap_or(new_node); + self.transitions.push(AnimationTransition { + duration, + old_node: previous_node, + new_node, + weight: 1.0, + }); + *old_node = Some(new_node); + + // Starts new animation, note we wont clear AnimationPlayer active animation hashmap here! + player.start(new_node) + } else { + panic!("Flow position 0 is out of bounds!"); } - - // 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 + /// Transitions the specified flow from its current node to a new node over a given duration. + /// + /// This method manages transitions within a specific flow, allowing multiple independent + /// state machines or animation layers to transition separately. If the flow has no previous node, + /// it will treat the `new_node` as both the old and new node during the transition. + pub fn transition_flows<'p>( + &mut self, + player: &'p mut AnimationPlayer, + new_node: AnimationNodeIndex, + flow_position: usize, + duration: Duration, + ) -> &'p mut ActiveAnimation { + // Check if flow exists + if let Some(old_node) = self.flows.get_mut(flow_position) { + // If is first time playing, just say old node equals new node + let previous_node = old_node.unwrap_or(new_node); + self.transitions.push(AnimationTransition { + duration, + old_node: previous_node, + new_node, + weight: 1.0, + }); + *old_node = Some(new_node); + + // Starts new animation, note we wont clear AnimationPlayer active animation hashmap here! + player.start(new_node) + } else { + panic!("Flow position {flow_position} is out of bounds!"); + } } } -/// A system that alters the weight of currently-playing transitions based on -/// the current time and decline amount. -pub fn advance_transitions( +/// 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 AnimationPlayer)>, time: Res