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