LCOV - code coverage report
Current view: top level - lib/src - sprite_animation.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 26 83 31.3 %
Date: 2021-08-10 15:50:53 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:ui';
       2             : 
       3             : import 'assets/images.dart';
       4             : import 'extensions/vector2.dart';
       5             : import 'flame.dart';
       6             : import 'sprite.dart';
       7             : 
       8             : export 'sprite.dart';
       9             : 
      10             : class SpriteAnimationFrameData {
      11             :   /// Coordinates of the sprite of this Frame
      12             :   final Vector2 srcPosition;
      13             : 
      14             :   /// Size of the sprite of this Frame
      15             :   final Vector2 srcSize;
      16             : 
      17             :   /// The duration to display it, in seconds.
      18             :   final double stepTime;
      19             : 
      20           0 :   SpriteAnimationFrameData({
      21             :     required this.srcPosition,
      22             :     required this.srcSize,
      23             :     required this.stepTime,
      24             :   });
      25             : }
      26             : 
      27             : class SpriteAnimationData {
      28             :   late List<SpriteAnimationFrameData> frames;
      29             :   final bool loop;
      30             : 
      31             :   /// Creates a SpriteAnimationData from the given [frames] and [loop] parameters
      32           0 :   SpriteAnimationData(this.frames, {this.loop = true});
      33             : 
      34             :   /// Takes some parameters and automatically calculate and create the frames for the sprite animation data
      35             :   ///
      36             :   /// [amount] The total amount of frames present on the image
      37             :   /// [stepTimes] A list of times (in seconds) of each frame, should have a length equals to the amount parameter
      38             :   /// [textureSize] The size of each frame
      39             :   /// [amountPerRow] An optional parameter to inform how many frames there are on which row, useful for sprite sheets where the frames as disposed on multiple lines
      40             :   /// [texturePosition] An optional parameter with the initial coordinate where the frames begin on the image, default to (top: 0, left: 0)
      41             :   /// [loop] An optional parameter to inform if this animation loops or has a single iteration, defaults to true
      42           0 :   SpriteAnimationData.variable({
      43             :     required int amount,
      44             :     required List<double> stepTimes,
      45             :     required Vector2 textureSize,
      46             :     int? amountPerRow,
      47             :     Vector2? texturePosition,
      48             :     this.loop = true,
      49           0 :   }) : assert(amountPerRow == null || amount >= amountPerRow) {
      50             :     amountPerRow ??= amount;
      51           0 :     texturePosition ??= Vector2.zero();
      52           0 :     frames = List<SpriteAnimationFrameData>.generate(amount, (i) {
      53           0 :       final position = Vector2(
      54           0 :         texturePosition!.x + (i % amountPerRow!) * textureSize.x,
      55           0 :         texturePosition.y + (i ~/ amountPerRow) * textureSize.y,
      56             :       );
      57           0 :       return SpriteAnimationFrameData(
      58           0 :         stepTime: stepTimes[i],
      59             :         srcPosition: position,
      60             :         srcSize: textureSize,
      61             :       );
      62             :     });
      63             :   }
      64             : 
      65             :   /// Works just like [SpriteAnimationData.variable] but uses the same [stepTime] for all frames
      66           0 :   factory SpriteAnimationData.sequenced({
      67             :     required int amount,
      68             :     required double stepTime,
      69             :     required Vector2 textureSize,
      70             :     int? amountPerRow,
      71             :     Vector2? texturePosition,
      72             :     bool loop = true,
      73             :   }) {
      74           0 :     return SpriteAnimationData.variable(
      75             :       amount: amount,
      76             :       amountPerRow: amountPerRow,
      77             :       texturePosition: texturePosition,
      78             :       textureSize: textureSize,
      79             :       loop: loop,
      80           0 :       stepTimes: List.filled(amount, stepTime),
      81             :     );
      82             :   }
      83             : }
      84             : 
      85             : /// Represents a single sprite animation frame.
      86             : class SpriteAnimationFrame {
      87             :   /// The [Sprite] to be displayed.
      88             :   Sprite sprite;
      89             : 
      90             :   /// The duration to display it, in seconds.
      91             :   double stepTime;
      92             : 
      93             :   /// Create based on the parameters.
      94           2 :   SpriteAnimationFrame(this.sprite, this.stepTime);
      95             : }
      96             : 
      97             : typedef OnCompleteSpriteAnimation = void Function();
      98             : 
      99             : /// Represents a sprite animation, that is, a list of sprites that change with time.
     100             : class SpriteAnimation {
     101             :   /// The frames that compose this animation.
     102             :   List<SpriteAnimationFrame> frames = [];
     103             : 
     104             :   /// Index of the current frame that should be displayed.
     105             :   int currentIndex = 0;
     106             : 
     107             :   /// Current clock time (total time) of this animation, in seconds, since last frame.
     108             :   ///
     109             :   /// It's ticked by the update method. It's reset every frame change.
     110             :   double clock = 0.0;
     111             : 
     112             :   /// Total elapsed time of this animation, in seconds, since start or a reset.
     113             :   double elapsed = 0.0;
     114             : 
     115             :   /// Whether the animation loops after the last sprite of the list, going back to the first, or keeps returning the last when done.
     116             :   bool loop = true;
     117             : 
     118             :   /// Registered method to be triggered when the animation complete.
     119             :   OnCompleteSpriteAnimation? onComplete;
     120             : 
     121             :   /// Creates an animation given a list of frames.
     122           0 :   SpriteAnimation(this.frames, {this.loop = true});
     123             : 
     124             :   /// Creates an empty animation
     125           0 :   SpriteAnimation.empty();
     126             : 
     127             :   /// Creates an animation based on the parameters.
     128             :   ///
     129             :   /// All frames have the same [stepTime].
     130           2 :   SpriteAnimation.spriteList(
     131             :     List<Sprite> sprites, {
     132             :     required double stepTime,
     133             :     this.loop = true,
     134             :   }) {
     135           2 :     if (sprites.isEmpty) {
     136           0 :       throw Exception('You must have at least one frame!');
     137             :     }
     138          10 :     frames = sprites.map((s) => SpriteAnimationFrame(s, stepTime)).toList();
     139             :   }
     140             : 
     141             :   /// Creates an SpriteAnimation based on its [data].
     142             :   ///
     143             :   /// Check [SpriteAnimationData] constructors for more info.
     144           0 :   SpriteAnimation.fromFrameData(
     145             :     Image image,
     146             :     SpriteAnimationData data,
     147             :   ) {
     148           0 :     frames = data.frames.map((frameData) {
     149           0 :       return SpriteAnimationFrame(
     150           0 :         Sprite(
     151             :           image,
     152           0 :           srcSize: frameData.srcSize,
     153           0 :           srcPosition: frameData.srcPosition,
     154             :         ),
     155           0 :         frameData.stepTime,
     156             :       );
     157           0 :     }).toList();
     158           0 :     loop = data.loop;
     159             :   }
     160             : 
     161             :   /// Automatically creates an Animation Object using animation data provided by the json file
     162             :   /// provided by Aseprite
     163             :   ///
     164             :   /// [imagePath]: Source of the sprite sheet animation
     165             :   /// [dataPath]: Animation's exported data in json format
     166           0 :   SpriteAnimation.fromAsepriteData(
     167             :     Image image,
     168             :     Map<String, dynamic> jsonData,
     169             :   ) {
     170           0 :     final jsonFrames = jsonData['frames'] as Map<String, dynamic>;
     171             : 
     172           0 :     final frames = jsonFrames.values.map((dynamic value) {
     173             :       final map = value as Map;
     174           0 :       final frameData = map['frame'] as Map<String, dynamic>;
     175           0 :       final x = frameData['x'] as int;
     176           0 :       final y = frameData['y'] as int;
     177           0 :       final width = frameData['w'] as int;
     178           0 :       final height = frameData['h'] as int;
     179             : 
     180           0 :       final stepTime = (map['duration'] as int) / 1000;
     181             : 
     182           0 :       final sprite = Sprite(
     183             :         image,
     184           0 :         srcPosition: Vector2Extension.fromInts(x, y),
     185           0 :         srcSize: Vector2Extension.fromInts(width, height),
     186             :       );
     187             : 
     188           0 :       return SpriteAnimationFrame(sprite, stepTime);
     189             :     });
     190             : 
     191           0 :     this.frames = frames.toList();
     192           0 :     loop = true;
     193             :   }
     194             : 
     195             :   /// Takes a path of an image, a [SpriteAnimationData] and loads the sprite animation
     196             :   /// When the [images] is omitted, the global [Flame.images] is used
     197           0 :   static Future<SpriteAnimation> load(
     198             :     String src,
     199             :     SpriteAnimationData data, {
     200             :     Images? images,
     201             :   }) async {
     202           0 :     final _images = images ?? Flame.images;
     203           0 :     final image = await _images.load(src);
     204           0 :     return SpriteAnimation.fromFrameData(image, data);
     205             :   }
     206             : 
     207             :   /// The current frame that should be displayed.
     208           8 :   SpriteAnimationFrame get currentFrame => frames[currentIndex];
     209             : 
     210             :   /// Returns whether the animation is on the last frame.
     211          12 :   bool get isLastFrame => currentIndex == frames.length - 1;
     212             : 
     213             :   /// Returns whether the animation has only a single frame (and is, thus, a still image).
     214           8 :   bool get isSingleFrame => frames.length == 1;
     215             : 
     216             :   /// Sets a different step time to each frame. The sizes of the arrays must match.
     217           0 :   set variableStepTimes(List<double> stepTimes) {
     218           0 :     assert(stepTimes.length == frames.length);
     219           0 :     for (var i = 0; i < frames.length; i++) {
     220           0 :       frames[i].stepTime = stepTimes[i];
     221             :     }
     222             :   }
     223             : 
     224             :   /// Sets a fixed step time to all frames.
     225           0 :   set stepTime(double stepTime) {
     226           0 :     frames.forEach((frame) => frame.stepTime = stepTime);
     227             :   }
     228             : 
     229             :   /// Resets the animation, like it would just have been created.
     230           1 :   void reset() {
     231           1 :     clock = 0.0;
     232           1 :     elapsed = 0.0;
     233           1 :     currentIndex = 0;
     234           1 :     _done = false;
     235             :   }
     236             : 
     237             :   /// Gets the current [Sprite] that should be shown.
     238             :   ///
     239             :   /// In case it reaches the end:
     240             :   ///  * If [loop] is true, it will return the last sprite. Otherwise, it will go back to the first.
     241           0 :   Sprite getSprite() {
     242           0 :     return currentFrame.sprite;
     243             :   }
     244             : 
     245             :   /// If [loop] is false, returns whether the animation is done (fixed in the last Sprite).
     246             :   ///
     247             :   /// Always returns false otherwise.
     248             :   bool _done = false;
     249           4 :   bool done() => _done;
     250             : 
     251             :   /// Updates this animation, ticking the lifeTime by an amount [dt] (in seconds).
     252           2 :   void update(double dt) {
     253           4 :     clock += dt;
     254           4 :     elapsed += dt;
     255           4 :     if (isSingleFrame || _done) {
     256             :       return;
     257             :     }
     258           8 :     while (clock >= currentFrame.stepTime) {
     259           2 :       if (isLastFrame) {
     260           2 :         if (loop) {
     261           8 :           clock -= currentFrame.stepTime;
     262           2 :           currentIndex = 0;
     263             :         } else {
     264           2 :           _done = true;
     265           3 :           onComplete?.call();
     266             :           return;
     267             :         }
     268             :       } else {
     269           8 :         clock -= currentFrame.stepTime;
     270           4 :         currentIndex++;
     271             :       }
     272             :     }
     273             :   }
     274             : 
     275             :   /// Returns a new Animation based on this animation, but with its frames in reversed order
     276           0 :   SpriteAnimation reversed() {
     277           0 :     return SpriteAnimation(frames.reversed.toList(), loop: loop);
     278             :   }
     279             : 
     280             :   /// Computes the total duration of this animation (before it's done or repeats).
     281           0 :   double totalDuration() {
     282           0 :     return frames.map((f) => f.stepTime).reduce((a, b) => a + b);
     283             :   }
     284             : }

Generated by: LCOV version 1.15