Skip to main content

fyrox_animation/machine/
state.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! State is a final "container" for animation pose. See [`State`] docs for more info.
22
23use crate::{
24    core::{
25        algebra::Vector2,
26        pool::{Handle, Pool},
27        rand::{self, seq::IteratorRandom},
28        reflect::prelude::*,
29        visitor::prelude::*,
30    },
31    machine::{AnimationPoseSource, ParameterContainer, PoseNode},
32    Animation, AnimationContainer, AnimationPose, EntityId,
33};
34use fxhash::FxHashSet;
35use fyrox_core::pool::PoolError;
36use fyrox_core::uuid::{uuid, Uuid};
37use fyrox_core::{NameProvider, TypeUuidProvider};
38use std::{
39    cell::Ref,
40    ops::{Deref, DerefMut},
41};
42use strum_macros::{AsRefStr, EnumString, VariantNames};
43
44#[doc(hidden)]
45#[derive(Default, Debug, Visit, Reflect, Clone, PartialEq)]
46pub struct StateActionWrapper<T: EntityId>(pub StateAction<T>);
47
48impl<T: EntityId> TypeUuidProvider for StateActionWrapper<T> {
49    fn type_uuid() -> Uuid {
50        uuid!("d686fac8-5cc1-46b1-82a4-7f4438cc078d")
51    }
52}
53
54impl<T: EntityId> Deref for StateActionWrapper<T> {
55    type Target = StateAction<T>;
56
57    fn deref(&self) -> &Self::Target {
58        &self.0
59    }
60}
61
62impl<T: EntityId> DerefMut for StateActionWrapper<T> {
63    fn deref_mut(&mut self) -> &mut Self::Target {
64        &mut self.0
65    }
66}
67
68/// An action, that will be executed by a state. It usually used to rewind, enable/disable animations
69/// when entering or leaving states. This is useful in situations when you have a one-shot animation
70/// and you need to rewind it before when entering some state. For example, you may have looped idle
71/// state and one-shot attack state. In this case, you need to use [`StateAction::RewindAnimation`]
72/// to tell the engine to automatically rewind the animation before using it. Otherwise, when the
73/// transition will happen, the animation could be ended already and you'll get "frozen" animation.
74#[derive(Default, Debug, Visit, Reflect, Clone, PartialEq, VariantNames, EnumString, AsRefStr)]
75pub enum StateAction<T: EntityId> {
76    /// No action.
77    #[default]
78    None,
79    /// Rewinds the animation.
80    RewindAnimation(Handle<Animation<T>>),
81    /// Enables the animation.
82    EnableAnimation(Handle<Animation<T>>),
83    /// Disables the animation.
84    DisableAnimation(Handle<Animation<T>>),
85    /// Enables random animation from the list. It could be useful if you want to add randomization
86    /// to your state machine. For example, you may have few melee attack animations and all of them
87    /// are suitable for every situation, in this case you can add randomization to make attacks less
88    /// predictable.
89    EnableRandomAnimation(Vec<Handle<Animation<T>>>),
90}
91
92impl<T: EntityId> TypeUuidProvider for StateAction<T> {
93    fn type_uuid() -> Uuid {
94        uuid!("c50a15cc-0f63-4409-bbe0-74b9d3e94755")
95    }
96}
97
98impl<T: EntityId> StateAction<T> {
99    /// Applies the action to the given animation container.
100    pub fn apply(&self, animations: &mut AnimationContainer<T>) {
101        match self {
102            StateAction::None => {}
103            StateAction::RewindAnimation(animation) => {
104                if let Ok(animation) = animations.try_get_mut(*animation) {
105                    animation.rewind();
106                }
107            }
108            StateAction::EnableAnimation(animation) => {
109                if let Ok(animation) = animations.try_get_mut(*animation) {
110                    animation.set_enabled(true);
111                }
112            }
113            StateAction::DisableAnimation(animation) => {
114                if let Ok(animation) = animations.try_get_mut(*animation) {
115                    animation.set_enabled(false);
116                }
117            }
118            StateAction::EnableRandomAnimation(animation_handles) => {
119                if let Some(animation) = animation_handles.iter().choose(&mut rand::thread_rng()) {
120                    if let Ok(animation) = animations.try_get_mut(*animation) {
121                        animation.set_enabled(true);
122                    }
123                }
124            }
125        }
126    }
127}
128
129/// State is a final "container" for animation pose. It has backing pose node which provides a set of values.
130/// States can be connected with each other using _transitions_, states with transitions form a state graph.
131#[derive(Default, Debug, Visit, Clone, Reflect, PartialEq)]
132pub struct State<T: EntityId> {
133    /// Position of state on the canvas. It is editor-specific data.
134    pub position: Vector2<f32>,
135
136    /// Name of the state.
137    pub name: String,
138
139    /// A set of actions that will be executed when entering the state.
140    #[visit(optional)]
141    pub on_enter_actions: Vec<StateActionWrapper<T>>,
142
143    /// A set of actions that will be executed when leaving the state.
144    #[visit(optional)]
145    pub on_leave_actions: Vec<StateActionWrapper<T>>,
146
147    /// Root node of the state that provides the state with animation data.
148    #[reflect(read_only)]
149    pub root: Handle<PoseNode<T>>,
150}
151
152impl<T: EntityId> NameProvider for State<T> {
153    fn name(&self) -> &str {
154        &self.name
155    }
156}
157
158impl<T: EntityId> State<T> {
159    /// Creates new instance of state with a given pose.
160    pub fn new(name: &str, root: Handle<PoseNode<T>>) -> Self {
161        Self {
162            position: Default::default(),
163            name: name.to_owned(),
164            on_enter_actions: Default::default(),
165            on_leave_actions: Default::default(),
166            root,
167        }
168    }
169
170    /// Returns a final pose of the state.
171    pub fn pose<'a>(
172        &self,
173        nodes: &'a Pool<PoseNode<T>>,
174    ) -> Result<Ref<'a, AnimationPose<T>>, PoolError> {
175        nodes.try_borrow(self.root).map(|root| root.pose())
176    }
177
178    /// Collects all animation handles used by this state.
179    pub fn collect_animations(
180        &self,
181        nodes: &Pool<PoseNode<T>>,
182        animations: &mut FxHashSet<Handle<Animation<T>>>,
183    ) {
184        if let Ok(root) = nodes.try_borrow(self.root) {
185            root.collect_animations(nodes, animations);
186        }
187    }
188
189    pub(super) fn update(
190        &mut self,
191        nodes: &Pool<PoseNode<T>>,
192        params: &ParameterContainer,
193        animations: &AnimationContainer<T>,
194        dt: f32,
195    ) {
196        if let Ok(root) = nodes.try_borrow(self.root) {
197            root.eval_pose(nodes, params, animations, dt);
198        }
199    }
200}