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}