Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_animation/src/transition.rs
6595 views
1
//! Animation transitions.
2
//!
3
//! Please note that this is an unstable temporary API. It may be replaced by a
4
//! state machine in the future.
5
6
use bevy_ecs::{
7
component::Component,
8
reflect::ReflectComponent,
9
system::{Query, Res},
10
};
11
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
12
use bevy_time::Time;
13
use core::time::Duration;
14
15
use crate::{graph::AnimationNodeIndex, ActiveAnimation, AnimationPlayer};
16
17
/// Manages fade-out of animation blend factors, allowing for smooth transitions
18
/// between animations.
19
///
20
/// To use this component, place it on the same entity as the
21
/// [`AnimationPlayer`] and [`AnimationGraphHandle`](crate::AnimationGraphHandle). It'll take
22
/// responsibility for adjusting the weight on the [`ActiveAnimation`] in order
23
/// to fade out animations smoothly.
24
///
25
/// When using an [`AnimationTransitions`] component, you should play all
26
/// animations through the [`AnimationTransitions::play`] method, rather than by
27
/// directly manipulating the [`AnimationPlayer`]. Playing animations through
28
/// the [`AnimationPlayer`] directly will cause the [`AnimationTransitions`]
29
/// component to get confused about which animation is the "main" animation, and
30
/// transitions will usually be incorrect as a result.
31
#[derive(Component, Default, Reflect)]
32
#[reflect(Component, Default, Clone)]
33
pub struct AnimationTransitions {
34
main_animation: Option<AnimationNodeIndex>,
35
transitions: Vec<AnimationTransition>,
36
}
37
38
// This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`.
39
impl Clone for AnimationTransitions {
40
fn clone(&self) -> Self {
41
Self {
42
main_animation: self.main_animation,
43
transitions: self.transitions.clone(),
44
}
45
}
46
47
fn clone_from(&mut self, source: &Self) {
48
self.main_animation = source.main_animation;
49
self.transitions.clone_from(&source.transitions);
50
}
51
}
52
53
/// An animation that is being faded out as part of a transition
54
#[derive(Debug, Clone, Copy, Reflect)]
55
#[reflect(Clone)]
56
pub struct AnimationTransition {
57
/// The current weight. Starts at 1.0 and goes to 0.0 during the fade-out.
58
current_weight: f32,
59
/// How much to decrease `current_weight` per second
60
weight_decline_per_sec: f32,
61
/// The animation that is being faded out
62
animation: AnimationNodeIndex,
63
}
64
65
impl AnimationTransitions {
66
/// Creates a new [`AnimationTransitions`] component, ready to be added to
67
/// an entity with an [`AnimationPlayer`].
68
pub fn new() -> AnimationTransitions {
69
AnimationTransitions::default()
70
}
71
72
/// Plays a new animation on the given [`AnimationPlayer`], fading out any
73
/// existing animations that were already playing over the
74
/// `transition_duration`.
75
///
76
/// Pass [`Duration::ZERO`] to instantly switch to a new animation, avoiding
77
/// any transition.
78
pub fn play<'p>(
79
&mut self,
80
player: &'p mut AnimationPlayer,
81
new_animation: AnimationNodeIndex,
82
transition_duration: Duration,
83
) -> &'p mut ActiveAnimation {
84
if let Some(old_animation_index) = self.main_animation.replace(new_animation)
85
&& let Some(old_animation) = player.animation_mut(old_animation_index)
86
&& !old_animation.is_paused()
87
{
88
self.transitions.push(AnimationTransition {
89
current_weight: old_animation.weight,
90
weight_decline_per_sec: 1.0 / transition_duration.as_secs_f32(),
91
animation: old_animation_index,
92
});
93
}
94
95
// If already transitioning away from this animation, cancel the transition.
96
// Otherwise the transition ending would incorrectly stop the new animation.
97
self.transitions
98
.retain(|transition| transition.animation != new_animation);
99
100
player.start(new_animation)
101
}
102
103
/// Obtain the currently playing main animation.
104
pub fn get_main_animation(&self) -> Option<AnimationNodeIndex> {
105
self.main_animation
106
}
107
}
108
109
/// A system that alters the weight of currently-playing transitions based on
110
/// the current time and decline amount.
111
pub fn advance_transitions(
112
mut query: Query<(&mut AnimationTransitions, &mut AnimationPlayer)>,
113
time: Res<Time>,
114
) {
115
// We use a "greedy layer" system here. The top layer (most recent
116
// transition) gets as much as weight as it wants, and the remaining amount
117
// is divided between all the other layers, eventually culminating in the
118
// currently-playing animation receiving whatever's left. This results in a
119
// nicely normalized weight.
120
for (mut animation_transitions, mut player) in query.iter_mut() {
121
let mut remaining_weight = 1.0;
122
123
for transition in &mut animation_transitions.transitions.iter_mut().rev() {
124
// Decrease weight.
125
transition.current_weight = (transition.current_weight
126
- transition.weight_decline_per_sec * time.delta_secs())
127
.max(0.0);
128
129
// Update weight.
130
let Some(ref mut animation) = player.animation_mut(transition.animation) else {
131
continue;
132
};
133
animation.weight = transition.current_weight * remaining_weight;
134
remaining_weight -= animation.weight;
135
}
136
137
if let Some(main_animation_index) = animation_transitions.main_animation
138
&& let Some(ref mut animation) = player.animation_mut(main_animation_index)
139
{
140
animation.weight = remaining_weight;
141
}
142
}
143
}
144
145
/// A system that removed transitions that have completed from the
146
/// [`AnimationTransitions`] object.
147
pub fn expire_completed_transitions(
148
mut query: Query<(&mut AnimationTransitions, &mut AnimationPlayer)>,
149
) {
150
for (mut animation_transitions, mut player) in query.iter_mut() {
151
animation_transitions.transitions.retain(|transition| {
152
let expire = transition.current_weight <= 0.0;
153
if expire {
154
player.stop(transition.animation);
155
}
156
!expire
157
});
158
}
159
}
160
161