bevy_tnua/
animating_helper.rs

1use std::mem::discriminant;
2
3use bevy::prelude::*;
4
5/// Utility for deciding which animation to play.
6///
7/// Add `TnuaAnimatingState<State>` as a component, where `State` is a data type - usually an
8/// `enum` - that determines which animation to play. Each frame, decide (with the help of
9/// [`TnuaController`](crate::prelude::TnuaController)) which animation should
10/// be played and the animation's parameters (like speed) and feed it to the `TnuaAnimatingState`.
11/// Use the emitted [`TnuaAnimatingStateDirective`] to determine if this is a new animation or an
12/// existing one (possibly with different parameters), and use that information to work the actual
13/// animation player.
14///
15/// ```
16/// # use bevy::prelude::*;
17/// # use bevy_tnua::prelude::*;
18/// # use bevy_tnua::{TnuaAnimatingState, TnuaAnimatingStateDirective};
19/// # use bevy_tnua::math::Float;
20/// # #[derive(Resource)]
21/// # struct AnimationNodes {
22/// #     standing: AnimationNodeIndex,
23/// #     running: AnimationNodeIndex,
24/// # }
25/// enum AnimationState {
26///     Standing,
27///     Running(Float),
28/// }
29///
30/// fn animating_system(
31///     mut query: &mut Query<(
32///         &mut TnuaAnimatingState<AnimationState>,
33///         &TnuaController,
34///         &mut AnimationPlayer,
35///     )>,
36///     animation_nodes: Res<AnimationNodes>,
37/// ) {
38///     for (mut animating_state, controller, mut animation_player) in query.iter_mut() {
39///         match animating_state.update_by_discriminant({
40///             let Some((_, basis_state)) = controller.concrete_basis::<TnuaBuiltinWalk>()
41///             else {
42///                 continue;
43///             };
44///             let speed = basis_state.running_velocity.length();
45///             if 0.01 < speed {
46///                 AnimationState::Running(speed)
47///             } else {
48///                 AnimationState::Standing
49///             }
50///         }) {
51///             TnuaAnimatingStateDirective::Maintain { state } => {
52///                 if let AnimationState::Running(speed) = state {
53///                     if let Some(active_animation) = animation_player.animation_mut(animation_nodes.running) {
54///                         active_animation.set_speed(*speed);
55///                     }
56///                 }
57///             }
58///             TnuaAnimatingStateDirective::Alter {
59///                 // We don't need the old state here, but it's available for transition
60///                 // animations.
61///                 old_state: _,
62///                 state,
63///             } => {
64///                 animation_player.stop_all();
65///                 match state {
66///                     AnimationState::Standing => {
67///                         animation_player
68///                             .start(animation_nodes.standing)
69///                             .set_speed(1.0)
70///                             .repeat();
71///                     }
72///                     AnimationState::Running(speed) => {
73///                         animation_player
74///                             .start(animation_nodes.running)
75///                             .set_speed(*speed)
76///                             .repeat();
77///                     }
78///                 }
79///             }
80///         }
81///     }
82/// }
83/// ```
84#[derive(Component)]
85pub struct TnuaAnimatingState<State> {
86    state: Option<State>,
87}
88
89impl<State> Default for TnuaAnimatingState<State> {
90    fn default() -> Self {
91        Self { state: None }
92    }
93}
94
95pub enum TnuaAnimatingStateDirective<'a, State> {
96    /// The animation to play remains the same - possibly with different parameters.
97    Maintain { state: &'a State },
98    /// A different animation needs to be played.
99    ///
100    /// Also returned (with `old_state: None`) if this is the first animation to be played.
101    Alter {
102        old_state: Option<State>,
103        state: &'a State,
104    },
105}
106
107impl<State> TnuaAnimatingState<State> {
108    /// Consider a new animation to play.
109    ///
110    /// The comparison function decides if its the same animation (possibly with different
111    /// parameters) or a different animation.
112    pub fn update_by(
113        &'_ mut self,
114        new_state: State,
115        comparison: impl FnOnce(&State, &State) -> bool,
116    ) -> TnuaAnimatingStateDirective<'_, State> {
117        let is_same = self
118            .state
119            .as_ref()
120            .is_some_and(|old_state| comparison(old_state, &new_state));
121        let old_state = self.state.replace(new_state);
122        if is_same {
123            TnuaAnimatingStateDirective::Maintain {
124                state: self.state.as_ref().expect("state was just placed there"),
125            }
126        } else {
127            TnuaAnimatingStateDirective::Alter {
128                old_state,
129                state: self.state.as_ref().expect("state was just placed there"),
130            }
131        }
132    }
133
134    /// Consider a new animation to play.
135    ///
136    /// The new animation is considered the same if and only if it is equal to the old animation.
137    pub fn update_by_value(&'_ mut self, new_state: State) -> TnuaAnimatingStateDirective<'_, State>
138    where
139        State: PartialEq,
140    {
141        self.update_by(new_state, |a, b| a == b)
142    }
143
144    /// Consider a new animation to play.
145    ///
146    /// The new animation is considered the same if it is the same variant of the enum as the old
147    /// animation.
148    ///
149    /// If the `State` is not an `enum`, using this method will not result in undefined behavior,
150    /// but the behavior is unspecified.
151    pub fn update_by_discriminant(
152        &'_ mut self,
153        new_state: State,
154    ) -> TnuaAnimatingStateDirective<'_, State> {
155        self.update_by(new_state, |a, b| discriminant(a) == discriminant(b))
156    }
157
158    /// Get the current state.
159    ///
160    /// Can provide no information about the previous state.
161    pub fn get(&self) -> Option<&State> {
162        self.state.as_ref()
163    }
164}