2013-03-30

Canvas Balloons

Canvas

Basic usage of the HTML canvas element is covered in the Canvas Crossroad post.


Picking colors

Balloons in the night look pretty awesome, although it's generally not advisable to fly them at night. The Balloons are displayed in various random colors, but the basic color has to be dark. The RGB color code describes the intensity of each color component. In order to keep the colors dark, one has to limit the amount of each color intensity to a rather low level. (in this case up to 50 from possible 255).

 function getBalloonColor() {

  var r = randomInt(50).toString(16);
  var g = randomInt(50).toString(16);
  var b = randomInt(50).toString(16);

  r = r.length < 2 ? '0' + r : r;
  g = g.length < 2 ? '0' + g : g;
  b = b.length < 2 ? '0' + b : b;

  return '#' + r + g + b;
 }

While the balloon changes climbing state it's color has to be darken or lighten by some percentage to simulate the flame. It's done by this function:

 function shadeColor(color, percent) {

  var num = parseInt(color.slice(1),16),
   amt = Math.round(2.55 * percent),
   R = (num >> 16) + amt,
   B = (num >> 8 & 0x00FF) + amt,
   G = (num & 0x0000FF) + amt;

   return "#" + (0x1000000 +
    (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
    (B < 255 ? B < 1 ? 0 : B : 255) * 0x100 +
    (G < 255 ? G < 1 ? 0 : G : 255)).toString(16).slice(1);
 }

The color of a climbing balloon should be darker further away from the flame. So a gradient is used. The HTML canvas gradient works only within a predefined range on a canvas. The example bellow shows a linear gradient from the top to the bottom of a balloon. Notice that the horizontal component is set to 0 (this is a vertical linear gradient):

 var linGrad = ctx.createLinearGradient(0, this.y, 0, this.y + this.height);

 linGrad.addColorStop(0, ballonColor);
 linGrad.addColorStop(1, shadeColor(ballonColor, ballonFlameLighter));

 ballonColor = linGrad;


Balloon shape

The balloon shape is drawn with

  • half circle
  • triangle
  • flame - small circle
  • basket - small rectangle
Since the balloons are moving, we have to repaint the sprite. I've simply used the same function for drawing with canvas background color instead of the balloon parts colors. If a clearRect method had been used the transition of a balloon over a star would look unnatural.
  // top balloon circle
  ctx.beginPath();
  ctx.arc(this.x + this.width / 2, this.y + this.radius,
    this.radius, Math.PI, 0, false);
  ctx.fill();

  // triangle
  ctx.beginPath();
  ctx.moveTo(this.x, this.y + this.radius - 1);
  ctx.lineTo(this.x + this.width / 2, this.y + this.height - this.boxHeight);
  ctx.lineTo(this.x + this.width / 2 + 1, this.y + this.height - this.boxHeight);
  ctx.lineTo(this.x + this.width, this.y + this.radius - 1);
  ctx.lineTo(this.x, this.y + this.radius - 1);
  ctx.fill();

  // flame
  if (this.climbing) {
   ctx.fillStyle = canvasBackground;
   ctx.beginPath();
   ctx.arc(this.x + this.width / 2, this.y + this.height - this.boxHeight,
     flameSize, 0, 2 * Math.PI, true);
   ctx.fill();
  }

  // basket
  ctx.fillStyle = gondolaColor;
  ctx.fillRect(
   this.x + this.width / 4 + this.width / 8,
   this.y + this.height - this.boxHeight,
   this.width / 4,
   this.boxHeight
  );


Collision detection

The collision detection between two balloons is bounding rectangles based. Before every balloon movement there is a collision check to the current positions of the other balloons. To detect collision between two rectangles I've used the following function:

 function checkCollision(obj1, obj2) {
  return !(
    (obj1.y + obj1.height < obj2.y) ||
    (obj1.y > obj2.y + obj2.height) ||
    (obj1.x > obj2.x + obj2.width) ||
    (obj1.x + obj1.width < obj2.x)
  );
 }


Detecting keyboard keyes

In order to make the simulation interactive the parameters can be controlled with the keyboard keys. The key detection in JavaScript is pretty straight forward:

 document.onkeydown = function(e) {
  if(e.keyCode == 37) {
   // left
  } else if(e.keyCode == 39) {
   // right
  }  else if(e.keyCode == 38) {
   // up
  } else if(e.keyCode == 40) {
   // down
  } else if (e.keyCode == 13) {
   // enter key
  }

  return false;
 };


And here's my example on:
Codepen
GitHub

No comments: