Saturday, January 24, 2015

Anansi: Sprite Animation


I was wondering about the possible techniques for sprite animation in JavaFX. So I searched the web and found a very good approach. Mike's Blog has an entry about Creating a Sprite Animation with JavaFX. Looks fairly easy to do.

We have to

  • subclass from Transition
  • implement the Transition class's interpolate method which is called in every frame
  • provide a sequence of Image objects
  • use ImageView to display each Image at a given interval

The code in the blog picks the images from a single image. We have separte images for each frame in the animation, so we have to modify the code a little.

Since we'll probably need more animations, we create a base class. Just like we did with the sprites.
public class AnimationBase extends Transition {

 private final ImageView imageView;
 private final int count;

 private int lastIndex;

 private Image[] sequence;

 public AnimationBase( Image[] sequence, double durationMs) {
  
  this.imageView = new ImageView( sequence[0]);
  this.sequence = sequence;
  this.count = sequence.length;

        setCycleCount( 1);
  setCycleDuration(Duration.millis( durationMs));
  setInterpolator(Interpolator.LINEAR);
  
 }

 protected void interpolate(double k) {
  
  final int index = Math.min((int) Math.floor(k * count), count - 1);
  if (index != lastIndex) {
   imageView.setImage(sequence[index]);
   lastIndex = index;
  }
  
 }
 
 public ImageView getView() {
  return imageView;
 }

}

With this we have the base for an animation. Now to the explosion itself. We may need multiple variations of explosions, so we create a dedicated explosion class. What do we need? The explosion happens at the sprite's location, so it's convenient if we hand over the sprite to calculate the location of the explosion. Then we need to put the view of the explosion on a layer. And we'd like to remove the exploding sprite after the explosion finishes. Let's get coding.
public class Explosion extends AnimationBase {

 public Explosion(Pane layer, SpriteBase explodingSprite, Image[] sequence, double durationMs) {
  
  super(sequence, durationMs);
  
  // explosions happen at the center of the object
  relocateToCenterOf(explodingSprite);
  
  // make explosion visible on screen
  layer.getChildren().add( getView());
  
  // remove objects when animation finishes
  setOnFinished(new EventHandler() {

   @Override
   public void handle(ActionEvent event) {
    
    // flag sprite to be removable
    explodingSprite.setRemovable(true);

    // stop animation
    stop();
    
    // remove explosion from screen
    layer.getChildren().remove( getView());
    
    
   }
  });
 }

 /**
  * Move the animation to the center of the given sprite, eg an explosion is on top of the enemy ship.
  * @param sprite
  */
 public void relocateToCenterOf( SpriteBase sprite) {
  getView().relocate( sprite.getCenterX() - getView().getImage().getWidth() / 2.0, sprite.getCenterY() - getView().getImage().getHeight() / 2.0);
 }
}

This gives us options to spawn an explosion at any sprite's location with as little code as:
private void spawnExplosion( SpriteBase sprite) {
 
  Explosion animation = new Explosion( playfieldLayer, sprite, explosionSequence, 400);
  animation.play();
 
}


We load the images for the explosion sequence the usual way in our resource loading method:
Image[] explosionSequence;
...
explosionSequence = new Image[ 10];
explosionSequence[0] = new Image( getClass().getResource("assets/effects/explosion/explosion_01/explosion_w100_01.png").toExternalForm());
explosionSequence[1] = new Image( getClass().getResource("assets/effects/explosion/explosion_01/explosion_w100_02.png").toExternalForm());
explosionSequence[2] = new Image( getClass().getResource("assets/effects/explosion/explosion_01/explosion_w100_03.png").toExternalForm());
explosionSequence[3] = new Image( getClass().getResource("assets/effects/explosion/explosion_01/explosion_w100_04.png").toExternalForm());
explosionSequence[4] = new Image( getClass().getResource("assets/effects/explosion/explosion_01/explosion_w100_05.png").toExternalForm());
explosionSequence[5] = new Image( getClass().getResource("assets/effects/explosion/explosion_01/explosion_w100_06.png").toExternalForm());
explosionSequence[6] = new Image( getClass().getResource("assets/effects/explosion/explosion_01/explosion_w100_07.png").toExternalForm());
explosionSequence[7] = new Image( getClass().getResource("assets/effects/explosion/explosion_01/explosion_w100_08.png").toExternalForm());
explosionSequence[8] = new Image( getClass().getResource("assets/effects/explosion/explosion_01/explosion_w100_09.png").toExternalForm());
explosionSequence[9] = new Image( getClass().getResource("assets/effects/explosion/explosion_01/explosion_w100_10.png").toExternalForm());

Looking at the resource loading code a resource handler would come in handy. One in which we specify a folder or a file name pattern and then the array gets created automatically depending on the files. Sooner or later we'll implement that. But let's get coding, we want to see explosions.

No comments:

Post a Comment