diff --git a/doc/flame/components.md b/doc/flame/components.md index c829103190..36157ece6a 100644 --- a/doc/flame/components.md +++ b/doc/flame/components.md @@ -364,6 +364,20 @@ this.player = SpriteAnimationComponent.fromFrameData( If you are not using `FlameGame`, don't forget this component needs to be updated, because the animation object needs to be ticked to move the frames. +To listen when the animation is done (when it reaches the last frame and is not looping) you can +use `animation.completed`. + +Example: + +```dart +await animation.completed; +doSomething(); + +// or alternatively + +animation.completed.whenComplete(doSomething); +``` + ## SpriteAnimationGroup diff --git a/packages/flame/lib/src/sprite_animation.dart b/packages/flame/lib/src/sprite_animation.dart index e5095de1e5..d7f913dc78 100644 --- a/packages/flame/lib/src/sprite_animation.dart +++ b/packages/flame/lib/src/sprite_animation.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:ui'; import 'assets/images.dart'; @@ -233,6 +234,8 @@ class SpriteAnimation { /// Registered method to be triggered when the animation complete. void Function()? onComplete; + Completer? _completeCompleter; + /// The current frame that should be displayed. SpriteAnimationFrame get currentFrame => frames[currentIndex]; @@ -243,6 +246,20 @@ class SpriteAnimation { /// still image). bool get isSingleFrame => frames.length == 1; + /// A future that will complete when the animation completes. + /// + /// An animation is considered to be completed if it reaches its [isLastFrame] + /// and is not [loop]ing. + Future get completed { + if (_done) { + return Future.value(); + } + + _completeCompleter ??= Completer(); + + return _completeCompleter!.future; + } + /// Sets a different step time to each frame. /// The sizes of the arrays must match. set variableStepTimes(List stepTimes) { @@ -307,6 +324,7 @@ class SpriteAnimation { } else { _done = true; onComplete?.call(); + _completeCompleter?.complete(); return; } } else { diff --git a/packages/flame/test/sprite_animation_test.dart b/packages/flame/test/sprite_animation_test.dart index 1cb8e98e41..afaa3f456a 100644 --- a/packages/flame/test/sprite_animation_test.dart +++ b/packages/flame/test/sprite_animation_test.dart @@ -44,9 +44,12 @@ void main() { test('onComplete called for single-frame animation', () { var counter = 0; final sprite = MockSprite(); - final animation = - SpriteAnimation.spriteList([sprite], stepTime: 1, loop: false) - ..onComplete = () => counter++; + final animation = SpriteAnimation.spriteList( + [sprite], + stepTime: 1, + loop: false, + )..onComplete = () => counter++; + expect(counter, 0); animation.update(0.5); expect(counter, 0); @@ -55,6 +58,71 @@ void main() { animation.update(1); expect(counter, 1); }); + + test('completed completes', () { + final sprite = MockSprite(); + final animation = SpriteAnimation.spriteList( + [sprite], + stepTime: 1, + loop: false, + ); + + expectLater(animation.completed, completes); + + animation.update(1); + }); + + test( + 'completed completes when the animation has already completed', + () async { + final sprite = MockSprite(); + final animation = SpriteAnimation.spriteList( + [sprite], + stepTime: 1, + loop: false, + ); + + animation.update(1); + + expectLater(animation.completed, completes); + }, + ); + + test( + "completed doesn't complete when the animation is yet to complete", + () async { + final sprite = MockSprite(); + final animation = SpriteAnimation.spriteList( + [sprite], + stepTime: 1, + loop: false, + ); + + expectLater(animation.completed, doesNotComplete); + }, + ); + + test( + "completed doesn't complete when animation is looping", + () async { + final sprite = MockSprite(); + final animation = SpriteAnimation.spriteList([sprite], stepTime: 1); + + expectLater(animation.completed, doesNotComplete); + }, + ); + + test( + "completed doesn't complete when animation is looping and on last frame", + () async { + final sprite = MockSprite(); + final animation = SpriteAnimation.spriteList([sprite], stepTime: 1); + + animation.update(1); + + expectLater(animation.completed, doesNotComplete); + }, + ); }); }