SpriteAnimationTicker in Flame to control an animation in Flutter

- Andrés Cruz

En español

SpriteAnimationTicker in Flame to control an animation in Flutter

An AnimationTicker is a technique used in various animation libraries and allow to control an animation; in the case of Flame, they also allow you to listen to the states of the animation; for example, when the animation is complete:

animationTicker.onComplete = () {
    ***
  }
};

When a frame of the animation is executed:

animationTicker.onFrame = (index) {
  ***
  }
};

This is all done by Flame's SpriteAnimationTicker class; for the game we are implementing, it is necessary to know when the "dying" animation ends to restart the game; for this, we can use any of the listeners shown above.

First, we need to initialize the ticker:

lib/components/player_component.dart

class PlayerComponent extends Character {
  ***
  late SpriteAnimationTicker deadAnimationTicker;

  @override
  void onLoad() async {
    ***
    deadAnimationTicker = deadAnimation.createTicker();
  }

  @override
  void update(double dt) {
    ***
    deadAnimationTicker.update(dt);
    super.update(dt);
  }
}

With the createTicker() function we create a ticker (SpriteAnimationTicker) on the animation that we are going to control; in the case of the game that we are implementing, we are interested in detecting when the dying animation ends, which will be executed only when the player runs out of lives and when the animation ends, the level restarts. The reason that the deadAnimationTicker property is initialized in the onLoad() and not when the deadAnimation animation is used (when the player runs out of lives) is that it is necessary to update the ticker in the upload() function according to the ticker life cycle:

deadAnimationTicker.update(dt)

With the ticket, a listener is created to detect when the animation has finished running; to do this, we can execute the onComplete() listener:

deadAnimationTicker.onComplete = () {
  // TODO
};

Or the onFrame() one, which is executed for each Frame, but, asking if the current frame is the last one:

deadAnimationTicker.onFrame = (index) {
  if (deadAnimationTicker.isLastFrame) {
    // TODO
  }
};

Finally, the complete code looks like:

lib/components/player_component.dart

class PlayerComponent extends Character {
  void reset({bool dead = false}) async {
    game.overlays.remove('Statistics');
    game.overlays.add('Statistics');
    velocity = Vector2.all(0);
    game.paused = false;
    blockPlayer = true;
    inviciblePlayer = true;
    movementType = MovementType.idle;
    if (dead) {
      animation = deadAnimation;

      deadAnimationTicker = deadAnimation.createTicker();
      deadAnimationTicker.onFrame = (index) {
        // print("-----" + index.toString());
        if (deadAnimationTicker.isLastFrame) {
          animation = idleAnimation;
          position =
              Vector2(spriteSheetWidth / 4, mapSize.y - spriteSheetHeight);
        }
      };

      deadAnimationTicker.onComplete = () {
        if (animation == deadAnimation) {
          animation = idleAnimation;
          position =
              Vector2(spriteSheetWidth / 4, mapSize.y - spriteSheetHeight);
        }
      };
    } else {
      animation = idleAnimation;
      position = Vector2(spriteSheetWidth / 4, mapSize.y - spriteSheetHeight);
      size = Vector2(spriteSheetWidth / 4, spriteSheetHeight / 4);
    }
    game.colisionMeteors = 0;
    game.addConsumibles();

    //position = Vector2(spriteSheetWidth / 4, 0);
  }
}

Video Transcript

If you have reached this point you have already seen these classes section after section to complete the first project as such or the first application that we really make which is the Dinosaur congratulations now yes as I told you initially before covering you in all this I recommended that you expand Play in its version 1.7. 3 which was the last one that I used to create that application but from that version as it happens with practically all versions of flame some slight changes occurred and therefore we have to adapt the game to work with the latest version of flame and more than this so that you also have the current references of how it basically works the components that we use is a current version that to date is 1.8. 1 of flame So, there are basically two changes we are going to deal with the first one that would be with the automobile animations previously so that we can define a callback for example to indicate when an animation was completed we simply have to indicate here the animation for example in Dead animation or any of these that are the ones we have defined here we place here for example this one of Animation point on compute And why did it not come out Basically, something that no longer comes out as such, you can see in this case in this project, I am already using the latest version of Flame, which to date would be as I indicated to you, 1.8. 1. So what is the question now? Well now we have to use a ticket, a Tinker, basically this is a component that will allow us to manage the animation, that is to say, the animations. We are not going to manipulate it directly, but in order to change it, we have to expand another class, an auxiliary, which in this case would be a ticket, that is basically it. So well, basically, well here we have several, we have several calms that we can use, the complete finger, they are the ones that seemed most interesting to me, that is to say, this is executed when the animation is complete. And this is executed basically frame by frame, so well, in this case, I am showing you here in the fragments of my book so that the idea is understood a little better. Here we basically have the Tinker that interests us, which is to define it for when the death animation is executed, since when this animation finishes executing is when we reset the game, that is to say, finish the animation:

class PlayerComponent extends Character {
  void reset({bool dead = false}) async {
    game.overlays.remove('Statistics');
    game.overlays.add('Statistics');
    velocity = Vector2.all(0);
    game.paused = false;
    blockPlayer = true;
    inviciblePlayer = true;
    movementType = MovementType.idle;
    if (dead) {
      animation = deadAnimation;

      deadAnimationTicker = deadAnimation.createTicker();
      deadAnimationTicker.onFrame = (index) {
        // print("-----" + index.toString());
        if (deadAnimationTicker.isLastFrame) {
          animation = idleAnimation;
          position =
              Vector2(spriteSheetWidth / 4, mapSize.y - spriteSheetHeight);
        }
      };

      deadAnimationTicker.onComplete = () {
        if (animation == deadAnimation) {
          animation = idleAnimation;
          position =
              Vector2(spriteSheetWidth / 4, mapSize.y - spriteSheetHeight);
        }
      };
    } else {
      animation = idleAnimation;
      position = Vector2(spriteSheetWidth / 4, mapSize.y - spriteSheetHeight);
      size = Vector2(spriteSheetWidth / 4, spriteSheetHeight / 4);
    }
    game.colisionMeteors = 0;
    game.addConsumibles();

    //position = Vector2(spriteSheetWidth / 4, 0);
  }
}

We place the Player at the beginning and refresh the overlays and also their associated properties So this is the property that we have to use as you can see here in the load we are initializing an object of the Tinker type which was what I was telling you before but here now we have a function called creaticker that we can execute directly from the animation the animation that of course here at this point is initialized and from here we have to implement it in a state, that is, every time the game is basically updated We also have to update the Tinker And this is so that the Tinker stays alive basically but the interesting part is that as I was telling you now the logic that we were using before I am going to show you this here in the project I better look for it here right here Well here the code is still a little messy but still you can understand since there are some things that I want to show you here as you can see when it dies This is the same function the recipe we pass the death flag Then this will be executed that again this piece or this section is the one that is executed when we want to restart the game by death of Player then here we can do it basically in two ways the most complex in quotes would be frame by frame detecting the last one which would be exactly this Note that here we already have all the control, that is to say this will be executed precisely based on this condition when the last frame is executed therefore it would be when it was already completed it is equivalent then when the animation of seeing Animation has already occurred then we execute all this which is to reposition the Player:

      deadAnimationTicker.onFrame = (index) {
        // print("-----" + index.toString());
        if (deadAnimationTicker.isLastFrame) {
          animation = idleAnimation;
          position =
              Vector2(spriteSheetWidth / 4, mapSize.y - spriteSheetHeight);
        }
      };

And down here we have the part to clean the connections and also here add all the consumables basically the reset the same as we had before here you can see the game here we have the dinosaur I'm going to let them crash me Remember that at the beginning we have a time to be invulnerable well here I already grabbed some food my God here one crashed me I don't want to grab the shield that another one crashed me and we'll see that another one appears over here there is no no for him to grab the shield there he died just as you can see and be the recalculation everything Just as you can see, that is to say, all this logic is working correctly Here you can also see depression Notice that here they appear Well This was from another print that I made when I put the video that just when we define the Animation we create the ticker again And this is to reset it at least This is how it works for me currently since the official documentation of all this is quite scarce and quite difficult to follow there you can review it if you want but anyway here again as I indicated the animation we indicate the death one because it is the one that has to happen and when it finishes when the death finishes happening then we reposition our character And from here we do the typical operations to reset the entire level, that is, place the consumables again and here the collisions reset them as well. Then you can do this either by measuring frame by frame. Here you can print it and you will see that this is printed that to date It would be good for the animation that we did would be n 8 frames You can see this here in the initialization up here are 8 from 0 to 8 But well You can also run this when it is completed here in the animation it is basically the same we can argue this another important point is that we no longer have the recipe here it is not necessary to run it either Remember that previously when we jumped or the Player jumped when we were here in the mushroom or a group Well I'm going to look here for a better recipe we had to restart the animation because if not it has stayed there in the last frame this is no longer necessary in the latest versions of Play therefore you can now remove it. Note that I jump here and I'm not restarting it it already does it automatically, something that always had to work like this but anyway well going back here to the one of a complete this would no longer be necessary as I was telling you here there will be good Exactly the same as we had before the one of indel and reposition it the one of 6 is not necessary I don't know why I have it there we can comment on this for a moment we reinitialize everything and wait to die again I'm going to come here Here one hit me that the other hit me I grabbed her the shield there one hit me one more is missing Okay I have the shield again there it is there it died look it works the same way and here you can see the devote impressions that I left so you can use either this scheme and ask about the last frame Or if you want to do something between frames you can do it there or you can use the one that would be more logical which would be that of a compliment But that is basically everything in summary we now have to create a ticker to control and know the status at all times of the animation in this case to hear when it is completed The death animation:

    if (dead) {
      animation = deadAnimation;

      deadAnimationTicker = deadAnimation.createTicker();
      deadAnimationTicker.onFrame = (index) {
        // print("-----" + index.toString());
        if (deadAnimationTicker.isLastFrame) {
          animation = idleAnimation;
          position =
              Vector2(spriteSheetWidth / 4, mapSize.y - spriteSheetHeight);
        }
      };

      deadAnimationTicker.onComplete = () {
        if (animation == deadAnimation) {
          animation = idleAnimation;
          position =
              Vector2(spriteSheetWidth / 4, mapSize.y - spriteSheetHeight);
        }
      };

And be able to execute the same logic that we had before repositioning our Player for the rest Everything remains exactly the same also remember another change what refers to the animations is that it is no longer necessary to reset them and automatically when the same is executed it is automatically restarted So again there is no need to do the reset manually and that would be all the changes you have to make So remember that you also have this change in the repository too Create an associated tag for exactly this version since surely in future versions of flame they will continue to change things so well That's all for now.

Andrés Cruz

Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz In Udemy

I agree to receive announcements of interest about this Blog.