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}