Chapter 6, Exercise 1 – Bubble Popping Animation Timing

When bubbles pop, the animation plays identically for every bubble. Experiment with changing the timing so that some bubbles play the animation faster and some slower. Also, try adding some rotation to the bubbles as they’re drawn onto the canvas. This should give the popping animation a much richer feel for very little effort.

We’re going to tackle both parts of this in the same way and at the same time, by adding more properties to the Sprite class. These will hold the rotation to draw a bubble at when it’s popping and also the time per frame that will be unique to each bubble.

Change Sprite.js to the following:

var BubbleShoot = window.BubbleShoot || {};
BubbleShoot.Sprite = (function($){
  var Sprite = function(){
    var that = this;
    var left;
    var top;
    var rotation = 0;
    var speed = 40;
    this.position = function(){
      return {
        left : left,
        top : top
      };
    };
    this.setPosition = function(args){
      if(arguments.length > 1){
        return;
      };
      if(args.left !== null)
        left = args.left;
      if(args.top !== null)
        top = args.top;
    };
    this.css = this.setPosition;
    this.animate = function(destination,config){
      var duration = config.duration;
      var animationStart = Date.now();
      var startPosition = that.position();
      that.updateFrame = function(){
        var elapsed = Date.now() - animationStart;
        var proportion = elapsed/duration;
        if(proportion > 1)
          proportion = 1;
        var posLeft = startPosition.left + (destination.left - startPosition.
            left) * proportion;
        var posTop = startPosition.top + (destination.top - startPosition.top)
          * proportion;
        that.css({
          left : posLeft,
          top : posTop
        });
      };
      setTimeout(function(){
        that.updateFrame = null;
        if(config.complete)
          config.complete();
      },duration);
    };
    this.setRotation = function(val){ rotation = val;};
    this.getRotation = function(){ return rotation;};
    this.setSpeed = function(val){ speed = val;};
    this.getSpeed = function(){ return speed;};
    return this;
  };
  Sprite.prototype.width = function(){
    return BubbleShoot.ui.BUBBLE_DIMS;
  };
  Sprite.prototype.height = function(){
    return BubbleShoot.ui.BUBBLE_DIMS;
  };
  Sprite.prototype.removeClass = function(){};
  Sprite.prototype.addClass = function(){};
  Sprite.prototype.remove = function(){};
  Sprite.prototype.kaboom = function(){
    jQuery.fn.kaboom.apply(this);
  };
  return Sprite;
})(jQuery);

Next, we need to set these values at the time that we pop bubbles. In game.js, change the popBubbles function:

var popBubbles = function(bubbles,delay){
  $.each(bubbles,function(){
    var bubble = this;
    setTimeout(function(){
      bubble.setState(BubbleShoot.BubbleState.POPPING);
      bubble.animatePop();
      bubble.getSprite().setRotation(Math.random() * Math.PI * 2);
      bubble.getSprite().setSpeed(20 + Math.random() * 60);
      setTimeout(function(){
        bubble.setState(BubbleShoot.BubbleState.POPPED);
      },bubble.getSprite().getSpeed() * 4);
    },delay);
    board.popBubbleAt(this.getRow(),this.getCol());
    setTimeout(function(){
      bubble.getSprite().remove();
    },delay + 200);
    delay += 60;
  });
};

And finally, in Renderer.js we want to recognise both the rotation angle and the new frame rate:

var BubbleShoot = window.BubbleShoot || {};
BubbleShoot.Renderer = (function($){
  var canvas;
  var context;
  var spriteSheet;
  var BUBBLE_IMAGE_DIM = 50;
  var Renderer = {
    init : function(callback){
      canvas = document.createElement("canvas");
      $(canvas).addClass("game_canvas");
      $("#game").prepend(canvas);
      $(canvas).attr("width",$(canvas).width());
      $(canvas).attr("height",$(canvas).height());
      context = canvas.getContext("2d");
      spriteSheet = new Image();
      spriteSheet.src = "_img/bubble_sprite_sheet.png";
      spriteSheet.onload = function() {
        callback();
      };
    },
    render : function(bubbles){
      context.clearRect(0,0,canvas.width,canvas.height);
      context.translate(120,0);
      $.each(bubbles,function(){
        var bubble = this;
        var clip = {
          top : bubble.getType() * BUBBLE_IMAGE_DIM,
          left : 0
        };
          switch(bubble.getState()){
            case BubbleShoot.BubbleState.POPPING:
              var timeInState = bubble.getTimeInState();
              if(timeInState < bubble.getSprite().getSpeed() * 2){
                clip.left = BUBBLE_IMAGE_DIM;
              }else if(timeInState < bubble.getSprite().getSpeed() * 3){
                clip.left = BUBBLE_IMAGE_DIM*2;
              }else{
                clip.left = BUBBLE_IMAGE_DIM*3;
              };
              break;
            case BubbleShoot.BubbleState.POPPED:
              return;
            case BubbleShoot.BubbleState.FIRED:
              return;
            case BubbleShoot.BubbleState.FALLEN:
              return;
          };
        Renderer.drawSprite(bubble.getSprite(),clip);
        context.restore();
      });
      context.translate(-120,0);
    },
    drawSprite : function(sprite,clip){
      context.save();
      context.translate(sprite.position().left + sprite.width()/2,sprite.
        position().top + sprite.height()/2);
      context.rotate(-sprite.getRotation());
      context.drawImage(spriteSheet,clip.left,clip.top,BUBBLE_IMAGE_DIM,
        BUBBLE_IMAGE_DIM,-sprite.width()/2,-sprite.height()/2,BUBBLE_IMAGE_DIM,
        BUBBLE_IMAGE_DIM);
      context.restore();
    }
  };
  return Renderer;
})(jQuery);

That should set the bubbles popping in a more varied fashion, although you won’t see a lot of difference unless you look closely. You would ideally pair this with a couple of different types of animation so that it’s virtually impossible for the user to detect that you’re re-using the same graphics, but even with this small change there is more visual variety than before.

Leave a Reply

Your email address will not be published. Required fields are marked *