How to write a BrikBloc game with HTML5 SVG and Canvas

Initialization

With our circle function, we can have an array of circles that we will initiate more and more close to us with a slight shift of the angle each time:

1.  // Initialization

2.  var circles = [];

3.

4.  var angle = Math.random() * Math.PI * 2.0;

5.

6.  var depth = maxDepth;

7.  var depthStep = maxDepth / circlesCount;

8.  var angleStep = (Math.PI * 2.0) / circlesCount;

9.  for (var index = 0; index < circlesCount; index++) {

10.circles[index] = new Circle(depth, angle, index % 5 == 0 ? 200 : 255);

11.

12.depth -= depthStep;

13.angle -= angleStep;

14.}

Computing FPS

We can compute FPS by measuring the amount of time between two calls to a given function. In our case, the function will be computeFPS. It will save the last 60 measures and will compute an average to produce the desired result:

1.  // FPS

2.  var previous = [];

3.  function computeFPS() {

4.  if (previous.length > 60) {

5.  previous.splice(0, 1);

6.  }

7.  var start = (new Date).getTime();

8.  previous.push(start);

9.  var sum = 0;

10.

11.for (var id = 0; id < previous.length - 1; id++) {

12.sum += previous[id + 1] - previous[id];

13.}

14.

15.var diff = 1000.0 / (sum / previous.length);

16.

17.stats.innerHTML = diff.toFixed() + " fps";

18.}

Drawing and animations

The canvas is a direct mode tool. This means that we have to reproduce all the content of the canvas every time we need to change something.

And first of all, we need to clear the content before each frame. The better solution to do that is to use clearRect:

1.  // Drawing & Animation

2.  function clearCanvas() {

3.  context.clearRect(0, 0, canvas.width, canvas.height);

4.  }

So the full wormhole drawing code will look like:

1.  function wormHole() {

2.  computeFPS();

3.  canvas.width = window.innerWidth;

4.  canvas.height = window.innerHeight - 130 - 40;

5.  clearCanvas();

6.  for (var index = 0; index < circlesCount; index++) {

7.  circles[index].draw();

8.  }

9.

10.circles.sort(function (a, b) {

11.if (a.depth > b.depth)

12.return -1;

13.if (a.depth < b.depth)

14.return 1;

15.return 0;

16.});

17.}

The sort code is used to prevent circles from overlapping.

Setting up mode button

To finalize our background, we just need to hook up the mode button to display or hide the background:

1.

2.  var wormHoleIntervalID = -1;

3.

4.  function startWormHole() {

5.  if (wormHoleIntervalID > -1)

6.  clearInterval(wormHoleIntervalID);

7.

8.  wormHoleIntervalID = setInterval(wormHole, 16);

9.

10.document.getElementById("wormHole").onclick = stopWormHole;

11.document.getElementById("wormHole").innerHTML = "Standard Mode";

12.}

13.

14.function stopWormHole() {

15.if (wormHoleIntervalID > -1)

16.clearInterval(wormHoleIntervalID);

17.

18.clearCanvas();

19.document.getElementById("wormHole").onclick = startWormHole;

20.document.getElementById("wormHole").innerHTML = "Wormhole Mode";

21.}

22.

23.stopWormHole();

Setting up the game

In order to simplify a bit the tutorial, the mouse handling code is already done. You can find all you need in the mouse.js file.

The background is handled by game.js file. So we have to register it inside index.htm. So right before the body closing tag, we will add the following code:

1.  <script type="text/javascript" src="game.js"></script>

Updating HTML5 page

The game will use SVG (Scalable Vector Graphics) to display the bricks, pad and ball. The SVG is a retained mode tool. So you don’t need to redraw all every time you want to move or change an item.

To add a SVG tag in our page, we just have to insert the following code (just after the canvas):

1.  <svg id="svgRoot">

2.  <circle cx="100" cy="100" r="10" id="ball" />

3.  <rect id="pad" height="15px" width="150px" x="200" y="200" rx="10" ry="20"/>

4.  </svg>

As you can note, the SVG starts with two already defined objects : a circle for the ball and a rectangle for the pad.

Defining constants and variables

In game.js file, we will start by adding some variables:

1.  // Getting elements

3.  var ball = document.getElementById("ball");

4.  var svg = document.getElementById("svgRoot");

5.  var message = document.getElementById("message");

The ball will be defined by:

·    A position

·    A speed

·    A direction

·    Its previous position

1.  // Ball

3.  var ballX;

4.  var ballY;

5.  var previousBallPosition = { x: 0, y: 0 };

6.  var ballDirectionX;

7.  var ballDirectionY;

8.  var ballSpeed = 10;

The pad will be defined by:

·    Width

·    Height

·    Position

·    Speed

·    Inertia value (just to make things smoother )

7.  var inertia = 0.80;

Bricks will be saved in an array and will be defined by:

·    Width

·    Height

·    Margin between them

·    Lines count

·    Columns count

We also need an offset and a variable for counting destroyed bricks.

1.  // Bricks

2.  var bricks = [];

3.  var destroyedBricksCount;

4.  var brickWidth = 50;

5.  var brickHeight = 20;

6.  var bricksRows = 5;

7.  var bricksCols = 20;

8.  var bricksMargin = 15;

9.  var bricksTop = 20;

And finally we also need the limits of the playground and a start date to compute session duration.

1.  // Misc.

4.  var maxX;

5.  var maxY;

6.  var startDate;

Handling a brick

To create a brick, we will need a function that will add a new element to the svg root. It will also configure each brick with required information:

1.  var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");

2.  svg.appendChild(rect);

3.

4.  rect.setAttribute("width", brickWidth);

5.  rect.setAttribute("height", brickHeight);

6.

7.  // Random green color

8.  var chars = "456789abcdef";

9.  var color = "";

10.for (var i = 0; i < 2; i++) {

11.var rnd = Math.floor(chars.length * Math.random());

12.color += chars.charAt(rnd);

13.}

14.rect.setAttribute("fill", "#00" + color + "00");

The brick function will also provide a drawAndCollide function to display a brick and to check if there is a collision with the ball:

1.  this.drawAndCollide = function () {

3.  return;

4.  // Drawing

5.  rect.setAttribute("x", position.x);

6.  rect.setAttribute("y", position.y);

7.

8.  // Collision

9.  if (ballX + ballRadius < position.x || ballX - ballRadius > position.x + brickWidth)

10.return;

11.

12.if (ballY + ballRadius < position.y || ballY - ballRadius > position.y + brickHeight)

13.return;

14.

16.this.remove();

18.destroyedBricksCount++;

19.

20.// Updating ball

21.ballX = previousBallPosition.x;

22.ballY = previousBallPosition.y;

23.

24.ballDirectionY *= -1.0;

25.};

Finally the full brick function will look like:

1.  // Brick function

2.  function Brick(x, y) {

4.  var position = { x: x, y: y };

5.

6.  var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");

7.  svg.appendChild(rect);

8.

9.  rect.setAttribute("width", brickWidth);

10.rect.setAttribute("height", brickHeight);

11.

12.// Random green color

13.var chars = "456789abcdef";

14.var color = "";

15.for (var i = 0; i < 2; i++) {

16.var rnd = Math.floor(chars.length * Math.random());

17.color += chars.charAt(rnd);

18.}

19.rect.setAttribute("fill", "#00" + color + "00");

20.

21.this.drawAndCollide = function () {

23.return;

24.// Drawing

25.rect.setAttribute("x", position.x);

26.rect.setAttribute("y", position.y);

27.

28.// Collision

29.if (ballX + ballRadius < position.x || ballX - ballRadius > position.x + brickWidth)

30.return;

31.

32.if (ballY + ballRadius < position.y || ballY - ballRadius > position.y + brickHeight)

33.return;

34.

36.this.remove();

38.destroyedBricksCount++;

39.

40.// Updating ball

41.ballX = previousBallPosition.x;

42.ballY = previousBallPosition.y;

43.

44.ballDirectionY *= -1.0;

45.};

46.

47.// Killing a brick

48.this.remove = function () {

50.return;

51.svg.removeChild(rect);

52.};

53.}

Collisions with the pad and the playground

The ball will also have collision functions that will handle collisions with the pad and the playground. These functions will have to update the ball direction when a collision will be detected.

1.  // Collisions

2.  function collideWithWindow() {

3.  if (ballX < minX) {

4.  ballX = minX;

5.  ballDirectionX *= -1.0;

6.  }

7.  else if (ballX > maxX) {

8.  ballX = maxX;

9.  ballDirectionX *= -1.0;

10.}

11.

12.if (ballY < minY) {

13.ballY = minY;

14.ballDirectionY *= -1.0;

15.}

16.else if (ballY > maxY) {

17.ballY = maxY;

18.ballDirectionY *= -1.0;

19.lost();

20.}

21.}

22.

25.return;

26.

28.return;

29.

30.ballX = previousBallPosition.x;

31.ballY = previousBallPosition.y;

32.ballDirectionY *= -1.0;

33.

35.

36.ballDirectionX = 2.0 * dist / padWidth;

37.

38.var square = Math.sqrt(ballDirectionX * ballDirectionX + ballDirectionY * ballDirectionY);

39.ballDirectionX /= square;

40.ballDirectionY /= square;

41.}

collideWithWindow checks the limits of the playground and collideWithPad checks the limits of the pad (We add a subtle change here: the horizontal speed of the ball will be computed using the distance with the center of the pad).

You can control the pad with the mouse or with the left and right arrows. The movePad function is responsible for handling pad movement. It will also handle the inertia:

4.

6.

9.

12.}

The code responsible for handling inputs is pretty simple:

1.  registerMouseMove(document.getElementById("gameZone"), function (posx, posy, previousX, previousY) {

2.  padSpeed += (posx - previousX) * 0.2;

3.  });

4.

6.  switch (evt.keyCode) {

7.  // Left arrow

8.  case 37:

10.break;

11.// Right arrow

12.case 39:

14.break;

15.}

16.}, true);

Game loop

Before setting up the game loop we need a function to define the playground size. This function will be called when window is resized.

1.  function checkWindow() {

2.  maxX = window.innerWidth - minX;

3.  maxY = window.innerHeight - 130 - 40 - minY;

4.  padY = maxY - 30;

5.  }

By the way, the game loop is the orchestrator here:

1.  function gameLoop() {

3.

4.  // Movements

5.  previousBallPosition.x = ballX;

6.  previousBallPosition.y = ballY;

7.  ballX += ballDirectionX * ballSpeed;

8.  ballY += ballDirectionY * ballSpeed;

9.

10.// Collisions

11.collideWithWindow();

13.

14.// Bricks

15.for (var index = 0; index < bricks.length; index++) {

16.bricks[index].drawAndCollide();

17.}

18.

19.// Ball

20.ball.setAttribute("cx", ballX);

21.ball.setAttribute("cy", ballY);

22.

26.

27.// Victory ?

28.if (destroyedBricksCount == bricks.length) {

29.win();

30.}

31.}

Initialization and victory

The first step of initialization is creating bricks:

1.  function generateBricks() {

2.  // Removing previous ones

3.  for (var index = 0; index < bricks.length; index++) {

4.  bricks[index].remove();

5.  }

6.

7.  // Creating new ones

8.  var brickID = 0;

9.

10.var offset = (window.innerWidth - bricksCols * (brickWidth + bricksMargin)) / 2.0;

11.

12.for (var x = 0; x < bricksCols; x++) {

13.for (var y = 0; y < bricksRows; y++) {

14.bricks[brickID++] = new Brick(offset + x * (brickWidth + bricksMargin), y * (brickHeight + bricksMargin) + bricksTop);

15.}

16.}

17.}

The next step is about setting variables used by the game:

1.  function initGame() {

2.  message.style.visibility = "hidden";

3.

4.  checkWindow();

5.

7.

8.  ballX = window.innerWidth / 2.0;

9.  ballY = maxY - 60;

10.

11.previousBallPosition.x = ballX;

12.previousBallPosition.y = ballY;

13.

15.

16.ballDirectionX = Math.random();

17.ballDirectionY = -1.0;

18.

19.generateBricks();

20.gameLoop();

21.}

Every time the user will change the window size, we will have to reset the game:

1.  window.onresize = initGame;

Then we have to attach an event handler to the new game button:

1.  var gameIntervalID = -1;

2.  function startGame() {

3.  initGame();

4.

5.  destroyedBricksCount = 0;

6.

7.  if (gameIntervalID > -1)

8.  clearInterval(gameIntervalID);

9.

10.startDate = (new Date()).getTime(); ;

11.gameIntervalID = setInterval(gameLoop, 16);

12.}

13.

14.document.getElementById("newGame").onclick = startGame;

Finally, we will add two functions for handling start and end of the game:

1.  var gameIntervalID = -1;

2.  function lost() {

3.  clearInterval(gameIntervalID);

4.  gameIntervalID = -1;

5.

6.  message.innerHTML = "Game over !";

7.  message.style.visibility = "visible";

8.  }

9.

10.function win() {

11.clearInterval(gameIntervalID);

12.gameIntervalID = -1;

13.

14.var end = (new Date).getTime();

15.

16.message.innerHTML = "Victory ! (" + Math.round((end - startDate) / 1000) + "s)";

17.message.style.visibility = "visible";

18.}

Conclusion

You are now a game developer! Using the power of accelerated graphics, we have developed a small game but with really interesting special effects!

It’s now up to you to update the game to make it the next blockbuster!

To go further

·    Learn about Internet Explorer 9/10 and why hardware acceleration matters

<<<>>>

David Catuhe is a developer evangelist for Microsoft France in charge of user experience development tools (from XAML to DirectX/XNA and HTML5). He defines himself as a geek and likes coding all that refer to graphics. Before working for Microsoft, he founded a company that developed a realtime 3D engine written with DirectX (www.vertice.fr)."

Page 2 of 2