33#![ warn( missing_docs) ]
44
55use std:: ops:: Deref ;
6+ use std:: time:: Duration ;
67
78use bevy_app:: { App , CoreStage , Plugin } ;
89use bevy_asset:: { AddAsset , Assets , Handle } ;
910use bevy_core:: Name ;
1011use bevy_ecs:: {
11- change_detection:: DetectChanges ,
12+ change_detection:: { DetectChanges , Mut } ,
1213 entity:: Entity ,
1314 prelude:: Component ,
1415 query:: With ,
@@ -114,22 +115,18 @@ impl AnimationClip {
114115 }
115116}
116117
117- /// Animation controls
118- #[ derive( Component , Reflect ) ]
119- #[ reflect( Component ) ]
120- pub struct AnimationPlayer {
121- paused : bool ,
118+ #[ derive( Reflect ) ]
119+ struct PlayingAnimation {
122120 repeat : bool ,
123121 speed : f32 ,
124122 elapsed : f32 ,
125123 animation_clip : Handle < AnimationClip > ,
126124 path_cache : Vec < Vec < Option < Entity > > > ,
127125}
128126
129- impl Default for AnimationPlayer {
127+ impl Default for PlayingAnimation {
130128 fn default ( ) -> Self {
131129 Self {
132- paused : false ,
133130 repeat : false ,
134131 speed : 1.0 ,
135132 elapsed : 0.0 ,
@@ -139,33 +136,106 @@ impl Default for AnimationPlayer {
139136 }
140137}
141138
139+ /// An animation that is being faded out as part of a transition
140+ struct AnimationTransition {
141+ /// The current weight. Starts at 1.0 and goes to 0.0 during the fade-out.
142+ current_weight : f32 ,
143+ /// How much to decrease `current_weight` per second
144+ weight_decline_per_sec : f32 ,
145+ /// The animation that is being faded out
146+ animation : PlayingAnimation ,
147+ }
148+
149+ /// Animation controls
150+ #[ derive( Component , Default , Reflect ) ]
151+ #[ reflect( Component ) ]
152+ pub struct AnimationPlayer {
153+ paused : bool ,
154+
155+ animation : PlayingAnimation ,
156+
157+ // List of previous animations we're currently transitioning away from.
158+ // Usually this is empty, when transitioning between animations, there is
159+ // one entry. When another animation transition happens while a transition
160+ // is still ongoing, then there can be more than one entry.
161+ // Once a transition is finished, it will be automatically removed from the list
162+ #[ reflect( ignore) ]
163+ transitions : Vec < AnimationTransition > ,
164+ }
165+
142166impl AnimationPlayer {
143167 /// Start playing an animation, resetting state of the player
168+ /// This will use a linear blending between the previous and the new animation to make a smooth transition
144169 pub fn start ( & mut self , handle : Handle < AnimationClip > ) -> & mut Self {
145- * self = Self {
170+ self . animation = PlayingAnimation {
146171 animation_clip : handle,
147172 ..Default :: default ( )
148173 } ;
174+
175+ // We want a hard transition.
176+ // In case any previous transitions are still playing, stop them
177+ self . transitions . clear ( ) ;
178+
179+ self
180+ }
181+
182+ /// Start playing an animation, resetting state of the player
183+ /// This will use a linear blending between the previous and the new animation to make a smooth transition
184+ pub fn start_with_transition (
185+ & mut self ,
186+ handle : Handle < AnimationClip > ,
187+ transition_duration : Duration ,
188+ ) -> & mut Self {
189+ let mut animation = PlayingAnimation {
190+ animation_clip : handle,
191+ ..Default :: default ( )
192+ } ;
193+ std:: mem:: swap ( & mut animation, & mut self . animation ) ;
194+
195+ // Add the current transition. If other transitions are still ongoing,
196+ // this will keep those transitions running and cause a transition between
197+ // the output of that previous transition to the new animation.
198+ self . transitions . push ( AnimationTransition {
199+ current_weight : 1.0 ,
200+ weight_decline_per_sec : 1.0 / transition_duration. as_secs_f32 ( ) ,
201+ animation,
202+ } ) ;
203+
149204 self
150205 }
151206
152207 /// Start playing an animation, resetting state of the player, unless the requested animation is already playing.
208+ /// If `transition_duration` is set, this will use a linear blending
209+ /// between the previous and the new animation to make a smooth transition
153210 pub fn play ( & mut self , handle : Handle < AnimationClip > ) -> & mut Self {
154- if self . animation_clip != handle || self . is_paused ( ) {
211+ if self . animation . animation_clip != handle || self . is_paused ( ) {
155212 self . start ( handle) ;
156213 }
157214 self
158215 }
159216
217+ /// Start playing an animation, resetting state of the player, unless the requested animation is already playing.
218+ /// This will use a linear blending between the previous and the new animation to make a smooth transition
219+ pub fn play_with_transition (
220+ & mut self ,
221+ handle : Handle < AnimationClip > ,
222+ transition_duration : Duration ,
223+ ) -> & mut Self {
224+ if self . animation . animation_clip != handle || self . is_paused ( ) {
225+ self . start_with_transition ( handle, transition_duration) ;
226+ }
227+ self
228+ }
229+
160230 /// Set the animation to repeat
161231 pub fn repeat ( & mut self ) -> & mut Self {
162- self . repeat = true ;
232+ self . animation . repeat = true ;
163233 self
164234 }
165235
166236 /// Stop the animation from repeating
167237 pub fn stop_repeating ( & mut self ) -> & mut Self {
168- self . repeat = false ;
238+ self . animation . repeat = false ;
169239 self
170240 }
171241
@@ -186,23 +256,23 @@ impl AnimationPlayer {
186256
187257 /// Speed of the animation playback
188258 pub fn speed ( & self ) -> f32 {
189- self . speed
259+ self . animation . speed
190260 }
191261
192262 /// Set the speed of the animation playback
193263 pub fn set_speed ( & mut self , speed : f32 ) -> & mut Self {
194- self . speed = speed;
264+ self . animation . speed = speed;
195265 self
196266 }
197267
198268 /// Time elapsed playing the animation
199269 pub fn elapsed ( & self ) -> f32 {
200- self . elapsed
270+ self . animation . elapsed
201271 }
202272
203273 /// Seek to a specific time in the animation
204274 pub fn set_elapsed ( & mut self , elapsed : f32 ) -> & mut Self {
205- self . elapsed = elapsed;
275+ self . animation . elapsed = elapsed;
206276 self
207277 }
208278}
@@ -283,37 +353,119 @@ pub fn animation_player(
283353 mut animation_players : Query < ( Entity , Option < & Parent > , & mut AnimationPlayer ) > ,
284354) {
285355 animation_players. par_for_each_mut ( 10 , |( root, maybe_parent, mut player) | {
286- let Some ( animation_clip) = animations. get ( & player. animation_clip ) else { return } ;
287- // Continue if paused unless the `AnimationPlayer` was changed
288- // This allow the animation to still be updated if the player.elapsed field was manually updated in pause
289- if player. paused && !player. is_changed ( ) {
290- return ;
291- }
292- if !player. paused {
293- player. elapsed += time. delta_seconds ( ) * player. speed ;
356+ update_transitions ( & mut player, & time) ;
357+ run_animation_player (
358+ root,
359+ player,
360+ & time,
361+ & animations,
362+ & names,
363+ & transforms,
364+ maybe_parent,
365+ & parents,
366+ & children,
367+ ) ;
368+ } ) ;
369+ }
370+
371+ #[ allow( clippy:: too_many_arguments) ]
372+ fn run_animation_player (
373+ root : Entity ,
374+ mut player : Mut < AnimationPlayer > ,
375+ time : & Time ,
376+ animations : & Assets < AnimationClip > ,
377+ names : & Query < & Name > ,
378+ transforms : & Query < & mut Transform > ,
379+ maybe_parent : Option < & Parent > ,
380+ parents : & Query < ( Option < With < AnimationPlayer > > , Option < & Parent > ) > ,
381+ children : & Query < & Children > ,
382+ ) {
383+ let paused = player. paused ;
384+ // Continue if paused unless the `AnimationPlayer` was changed
385+ // This allow the animation to still be updated if the player.elapsed field was manually updated in pause
386+ if paused && !player. is_changed ( ) {
387+ return ;
388+ }
389+
390+ // Apply the main animation
391+ apply_animation (
392+ 1.0 ,
393+ & mut player. animation ,
394+ paused,
395+ root,
396+ time,
397+ animations,
398+ names,
399+ transforms,
400+ maybe_parent,
401+ parents,
402+ children,
403+ ) ;
404+
405+ // Apply any potential fade-out transitions from previous animations
406+ for AnimationTransition {
407+ current_weight,
408+ animation,
409+ ..
410+ } in & mut player. transitions
411+ {
412+ apply_animation (
413+ * current_weight,
414+ animation,
415+ paused,
416+ root,
417+ time,
418+ animations,
419+ names,
420+ transforms,
421+ maybe_parent,
422+ parents,
423+ children,
424+ ) ;
425+ }
426+ }
427+
428+ #[ allow( clippy:: too_many_arguments) ]
429+ fn apply_animation (
430+ weight : f32 ,
431+ animation : & mut PlayingAnimation ,
432+ paused : bool ,
433+ root : Entity ,
434+ time : & Time ,
435+ animations : & Assets < AnimationClip > ,
436+ names : & Query < & Name > ,
437+ transforms : & Query < & mut Transform > ,
438+ maybe_parent : Option < & Parent > ,
439+ parents : & Query < ( Option < With < AnimationPlayer > > , Option < & Parent > ) > ,
440+ children : & Query < & Children > ,
441+ ) {
442+ if let Some ( animation_clip) = animations. get ( & animation. animation_clip ) {
443+ if !paused {
444+ animation. elapsed += time. delta_seconds ( ) * animation. speed ;
294445 }
295- let mut elapsed = player . elapsed ;
296- if player . repeat {
446+ let mut elapsed = animation . elapsed ;
447+ if animation . repeat {
297448 elapsed %= animation_clip. duration ;
298449 }
299450 if elapsed < 0.0 {
300451 elapsed += animation_clip. duration ;
301452 }
302- if player . path_cache . len ( ) != animation_clip. paths . len ( ) {
303- player . path_cache = vec ! [ Vec :: new( ) ; animation_clip. paths. len( ) ] ;
453+ if animation . path_cache . len ( ) != animation_clip. paths . len ( ) {
454+ animation . path_cache = vec ! [ Vec :: new( ) ; animation_clip. paths. len( ) ] ;
304455 }
305- if !verify_no_ancestor_player ( maybe_parent, & parents) {
456+ if !verify_no_ancestor_player ( maybe_parent, parents) {
306457 warn ! ( "Animation player on {:?} has a conflicting animation player on an ancestor. Cannot safely animate." , root) ;
307458 return ;
308459 }
460+
309461 for ( path, bone_id) in & animation_clip. paths {
310- let cached_path = & mut player . path_cache [ * bone_id] ;
462+ let cached_path = & mut animation . path_cache [ * bone_id] ;
311463 let curves = animation_clip. get_curves ( * bone_id) . unwrap ( ) ;
312- let Some ( target) = find_bone ( root, path, & children, & names, cached_path) else { continue } ;
464+ let Some ( target) = find_bone ( root, path, children, names, cached_path) else { continue } ;
313465 // SAFETY: The verify_no_ancestor_player check above ensures that two animation players cannot alias
314466 // any of their descendant Transforms.
315- //
316- // The system scheduler prevents any other system from mutating Transforms at the same time,
467+ //
468+ // The system scheduler prevents any other system from mutating Transforms at the same time,
317469 // so the only way this fetch can alias is if two AnimationPlayers are targetting the same bone.
318470 // This can only happen if there are two or more AnimationPlayers are ancestors to the same
319471 // entities. By verifying that there is no other AnimationPlayer in the ancestors of a
@@ -327,11 +479,16 @@ pub fn animation_player(
327479 // Some curves have only one keyframe used to set a transform
328480 if curve. keyframe_timestamps . len ( ) == 1 {
329481 match & curve. keyframes {
330- Keyframes :: Rotation ( keyframes) => transform. rotation = keyframes[ 0 ] ,
482+ Keyframes :: Rotation ( keyframes) => {
483+ transform. rotation = transform. rotation . slerp ( keyframes[ 0 ] , weight) ;
484+ }
331485 Keyframes :: Translation ( keyframes) => {
332- transform. translation = keyframes[ 0 ] ;
486+ transform. translation =
487+ transform. translation . lerp ( keyframes[ 0 ] , weight) ;
488+ }
489+ Keyframes :: Scale ( keyframes) => {
490+ transform. scale = transform. scale . lerp ( keyframes[ 0 ] , weight) ;
333491 }
334- Keyframes :: Scale ( keyframes) => transform. scale = keyframes[ 0 ] ,
335492 }
336493 continue ;
337494 }
@@ -362,24 +519,31 @@ pub fn animation_player(
362519 rot_end = -rot_end;
363520 }
364521 // Rotations are using a spherical linear interpolation
365- transform . rotation =
366- rot_start . normalize ( ) . slerp ( rot_end . normalize ( ) , lerp ) ;
522+ let rot = rot_start . normalize ( ) . slerp ( rot_end . normalize ( ) , lerp ) ;
523+ transform . rotation = transform . rotation . slerp ( rot , weight ) ;
367524 }
368525 Keyframes :: Translation ( keyframes) => {
369526 let translation_start = keyframes[ step_start] ;
370527 let translation_end = keyframes[ step_start + 1 ] ;
371528 let result = translation_start. lerp ( translation_end, lerp) ;
372- transform. translation = result;
529+ transform. translation = transform . translation . lerp ( result, weight ) ;
373530 }
374531 Keyframes :: Scale ( keyframes) => {
375532 let scale_start = keyframes[ step_start] ;
376533 let scale_end = keyframes[ step_start + 1 ] ;
377534 let result = scale_start. lerp ( scale_end, lerp) ;
378- transform. scale = result;
535+ transform. scale = transform . scale . lerp ( result, weight ) ;
379536 }
380537 }
381538 }
382539 }
540+ }
541+ }
542+
543+ fn update_transitions ( player : & mut AnimationPlayer , time : & Time ) {
544+ player. transitions . retain_mut ( |animation| {
545+ animation. current_weight -= animation. weight_decline_per_sec * time. delta_seconds ( ) ;
546+ animation. current_weight > 0.0
383547 } ) ;
384548}
385549
0 commit comments