Build HTML5 Games in JavaScript

Build HTML5 Games in JavaScript

Table of Contents

  1. Create the Canvas and draw on it
  2. Move the ball
  3. Bounce off the walls
  4. Paddle and keyboard controls
  5. Game over
  6. Build the brick field
  7. Collision detection
  8. Track the score and win
  9. Mouse controls
  10. Finishing up

Breakout

Breakout gameplay

0. Preparation

  • Basic JavaScript knowledge
  • Open mind, eager to learn

1. Create the Canvas and draw on it

HTML structure

        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="utf-8" />
            <title>Gamedev Canvas Workshop</title>
            <style>
              * { padding: 0; margin: 0; }
              canvas { background: #eee; display: block; margin: 0 auto; }
            </style>
        </head>
        <body>
        <canvas id="myCanvas" width="480" height="320"></canvas>
        <script>
          // JavaScript code goes here
        </script>
        </body>
        </html>
      

Initialize the handle

        var canvas = document.getElementById("myCanvas");
        var ctx = canvas.getContext("2d");
      

Draw on the Canvas

        ctx.beginPath();
        ctx.rect(20, 40, 50, 50);
        ctx.fillStyle = "#FF0000";
        ctx.fill();
        ctx.closePath();
      

Draw shapes

        ctx.beginPath();
        ctx.arc(240, 160, 20, 0, Math.PI*2, false);
        ctx.fillStyle = "green";
        ctx.fill();
        ctx.closePath();
      

Draw in different style

        ctx.beginPath();
        ctx.rect(160, 10, 100, 40);
        ctx.strokeStyle = "rgba(0, 0, 255, 0.5)";
        ctx.stroke();
        ctx.closePath();
      

Screenshot 1

Screenshot 1

Exercise 1

Change the size and color of the given shapes.

Source code 1

2. Move the ball

Draw loop

        function draw() {
          // drawing code
        }
        setInterval(draw, 10);
      

Drawing the ball

        ctx.beginPath();
        ctx.arc(50, 50, 10, 0, Math.PI*2);
        ctx.fillStyle = "#0095DD";
        ctx.fill();
        ctx.closePath();
      

Starting point

        var x = canvas.width/2;
        var y = canvas.height-30;
      

Variables instead of constants

        function draw() {
          ctx.beginPath();
          ctx.arc(x, y, 10, 0, Math.PI*2);
          ctx.fillStyle = "#0095DD";
          ctx.fill();
          ctx.closePath();
        }
      
      
    

Delta

        var dx = 2;
        var dy = -2;
      

Draw with delta

        function draw() {
          ctx.beginPath();
          ctx.arc(x, y, 10, 0, Math.PI*2);
          ctx.fillStyle = "#0095DD";
          ctx.fill();
          ctx.closePath();
          x += dx;
          y += dy;
        }
      

Ooops!

Breakout ball trail

Clear the Canvas

        function draw() {
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          ctx.beginPath();
          ctx.arc(x, y, 10, 0, Math.PI*2);
          ctx.fillStyle = "#0095DD";
          ctx.fill();
          ctx.closePath();
          x += dx;
          y += dy;
        }
      

Make it tidy

        function drawBall() {
          ctx.beginPath();
          ctx.arc(x, y, 10, 0, Math.PI*2);
          ctx.fillStyle = "#0095DD";
          ctx.fill();
          ctx.closePath();
        }
        function draw() {
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          drawBall();
          x += dx;
          y += dy;
        }
      

Screenshot 2

Screenshot 2

Exercise 2

Change the speed of the moving ball, or the direction it moves in.

Source code 2

3. Bounce off the walls

Simple collision detection

        var ballRadius = 10;
        ctx.arc(x, y, ballRadius, 0, Math.PI*2);
      

Bouncing off - the top

        if(y + dy < 0) {
          dy = -dy;
        }
      

Bouncing off - the bottom

        if(y + dy > canvas.height) {
          dy = -dy;
        }
      

Bouncing off the top and bottom

        if(y + dy > canvas.height || y + dy < 0) {
          dy = -dy;
        }
      

Bouncing off all four directions

        if(x + dx > canvas.width || x + dx < 0) {
          dx = -dx;
        }
        if(y + dy > canvas.height || y + dy < 0) {
          dy = -dy;
        }
      

Ooops!

Breakout ball in wall

Exact collision point

        if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) {
          dx = -dx;
        }
        if(y + dy > canvas.height-ballRadius || y + dy < ballRadius) {
          dy = -dy;
        }
      

Screenshot 3

Screenshot 3

Exercise 3

Change the color of the ball to a random colour every time it hits the wall.

Source code 3

4. Paddle and keyboard controls

Paddle

        var paddleHeight = 10;
        var paddleWidth = 75;
        var paddleX = (canvas.width-paddleWidth)/2;
      

Draw the paddle

        function drawPaddle() {
          ctx.beginPath();
          ctx.rect(paddleX, canvas.height-paddleHeight,
            paddleWidth, paddleHeight);
          ctx.fillStyle = "#0095DD";
          ctx.fill();
          ctx.closePath();
        }
      

How to control the paddle

  • Two variables for left and right button presses
  • Two event listeners for keydown and keyup events
  • Two functions handling the keydown and keyup events
  • Ability to actually move the paddle left and right

Variables storing key press

        var rightPressed = false;
        var leftPressed = false;
      

Keyboard events

        document.addEventListener("keydown", keyDownHandler, false);
        document.addEventListener("keyup", keyUpHandler, false);
      

Event functions - down

        function keyDownHandler(e) {
          if(e.keyCode == 39) {
            rightPressed = true;
          }
          else if(e.keyCode == 37) {
            leftPressed = true;
          }
        }
      

Event functions - up

        function keyUpHandler(e) {
          if(e.keyCode == 39) {
            rightPressed = false;
          }
          else if(e.keyCode == 37) {
            leftPressed = false;
          }
        }
      

Move the paddle

        if(rightPressed) {
          paddleX += 7;
        }
        else if(leftPressed) {
          paddleX -= 7;
        }
      

Limit the movement

        if(rightPressed && paddleX < canvas.width-paddleWidth) {
          paddleX += 7;
        }
        else if(leftPressed && paddleX > 0) {
          paddleX -= 7;
        }
      

Draw everything

        function draw() {
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          drawBall();
          drawPaddle();
          // ...
        }
      

Screenshot 4

Screenshot 4

Exercise 4

Make the paddle move faster or slower, or change its size.

Source code 4

5. Game over

Bouncing off the walls

        if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) {
          dx = -dx;
        }
        if(y + dy > canvas.height-ballRadius || y + dy < ballRadius) {
          dy = -dy;
        }
      

Horizontal bounce

        if(x + dx < ballRadius) {
          dx = -dx;
        } else if(x + dx > canvas.width-ballRadius) {
          dx = -dx;
        }
      

Vertical bounce

        if(y + dy < ballRadius) {
          dy = -dy;
        } else if(y + dy > canvas.height-ballRadius) {
          alert("GAME OVER");
          document.location.reload();
        }
      

Hit the ball with the paddle

        if(y + dy < ballRadius) {
          dy = -dy;
        } else if(y + dy > canvas.height-ballRadius) {
          if(x > paddleX && x < paddleX + paddleWidth) {
            dy = -dy;
          }
          else {
            alert("GAME OVER");
            document.location.reload();
          }
        }
      

Screenshot 5

Screenshot 5

Exercise 5

Make the ball move faster when it hits the paddle.

Source code 5

6. Build the brick field

Brick variables

        var brickRowCount = 5;
        var brickColumnCount = 3;
        var brickWidth = 75;
        var brickHeight = 20;
        var brickPadding = 10;
        var brickOffsetTop = 30;
        var brickOffsetLeft = 30;
      

2D array of bricks

        var bricks = [];
        for(c=0; c<brickColumnCount; c++) {
          bricks[c] = [];
          for(r=0; r<brickRowCount; r++) {
            bricks[c][r] = { x: 0, y: 0 };
          }
        }
      

Drawing the bricks

        function drawBricks() {
          for(c=0; c<brickColumnCount; c++) {
            for(r=0; r<brickRowCount; r++) {
              bricks[c][r].x = 0;
              bricks[c][r].y = 0;
              ctx.beginPath();
              ctx.rect(0, 0, brickWidth, brickHeight);
              ctx.fillStyle = "#0095DD";
              ctx.fill();
              ctx.closePath();
            }
          }
        }
      

Position logic

        var brickX = (r*(brickWidth+brickPadding))+brickOffsetLeft;
        var brickY = (c*(brickHeight+brickPadding))+brickOffsetTop;
      

Proper draw

        function drawBricks() {
          for(c=0; c<brickColumnCount; c++) {
            for(r=0; r<brickRowCount; r++) {
              var brickX = (r*(brickWidth+brickPadding))+brickOffsetLeft;
              var brickY = (c*(brickHeight+brickPadding))+brickOffsetTop;
              bricks[c][r].x = brickX;
              bricks[c][r].y = brickY;
              ctx.beginPath();
              ctx.rect(brickX, brickY, brickWidth, brickHeight);
              ctx.fillStyle = "#0095DD";
              ctx.fill();
              ctx.closePath();
            }
          }
        }
      

Draw everything

        function draw() {
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          drawBricks();
          drawBall();
          drawPaddle();
          // ...
        }
      

Screenshot 6

Screenshot 6

Exercise 6

Change the number of bricks in a row or a column, or their positions.

Source code 6

7. Collision detection

Collision detection function

        function collisionDetection() {
          for(c=0; c<brickColumnCount; c++) {
            for(r=0; r<brickRowCount; r++) {
              var b = bricks[c][r];
              // calculations
            }
          }
        }
      

Collision logic

  • The x position of the ball is greater than the x position of the brick.
  • The x position of the ball is less than the x position of the brick minus its width.
  • The y position of the ball is greater than the y position of the brick.
  • The y position of the ball is less than the y position of the brick minus its width.

Collision condition

        function collisionDetection() {
          for(c=0; c<brickColumnCount; c++) {
            for(r=0; r<brickRowCount; r++) {
              var b = bricks[c][r];
              if(x > b.x && x < b.x+brickWidth &&
                y > b.y && y < b.y+brickHeight) {
                dy = -dy;
              }
            }
          }
        }
      

Status of the brick

        var bricks = [];
        for(c=0; c<brickColumnCount; c++) {
          bricks[c] = [];
          for(r=0; r<brickRowCount; r++) {
            bricks[c][r] = { x: 0, y: 0, status: 1 };
          }
        }
      

Draw if not hidden

        function drawBricks() {
          for(c=0; c<brickColumnCount; c++) {
            for(r=0; r<brickRowCount; r++) {
              if(bricks[c][r].status == 1) {
                var brickX = (r*(brickWidth+brickPadding))+brickOffsetLeft;
                var brickY = (c*(brickHeight+brickPadding))+brickOffsetTop;
                bricks[c][r].x = brickX;
                bricks[c][r].y = brickY;
                ctx.beginPath();
                ctx.rect(brickX, brickY, brickWidth, brickHeight);
                ctx.fillStyle = "#0095DD";
                ctx.fill();
                ctx.closePath();
              }
            }
          }
        }
      

Hide when hit

        function collisionDetection() {
          for(c=0; c<brickColumnCount; c++) {
            for(r=0; r<brickRowCount; r++) {
              var b = bricks[c][r];
              if(b.status == 1) {
                if(x > b.x && x < b.x+brickWidth &&
                  y > b.y && y < b.y+brickHeight) {
                  dy = -dy;
                  b.status = 0;
                }
              }
            }
          }
        }
      

Execute everything

        function draw() {
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          drawBricks();
          drawBall();
          drawPaddle();
          collisionDetection();
          // ...
        }
      

Screenshot 7

Screenshot 7

Exercise 7

Change the color of the ball when it hits the brick.

Source code 7

8. Track the score and win

Score variable

        var score = 0;
      

Drawing the score

        function drawScore() {
          ctx.font = "16px Arial";
          ctx.fillStyle = "#0095DD";
          ctx.fillText("Score: "+score, 8, 20);
        }
      

Increasing the score

        function collisionDetection() {
          for(c=0; c<brickColumnCount; c++) {
            for(r=0; r<brickRowCount; r++) {
              var b = bricks[c][r];
              if(x > b.x && x < b.x+brickWidth &&
                y > b.y && y < b.y+brickHeight) {
                dy = -dy;
                b.status = 0;
                score++;
              }
            }
          }
        }
      

Print it out

        function draw() {
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          drawBricks();
          drawBall();
          drawPaddle();
          collisionDetection();
          drawScore();
          // ...
        }
      

Win condition

        function collisionDetection() {
          for(c=0; c<brickColumnCount; c++) {
            for(r=0; r<brickRowCount; r++) {
              var b = bricks[c][r];
              if(x > b.x && x < b.x+brickWidth &&
                y > b.y && y < b.y+brickHeight) {
                dy = -dy;
                b.status = 0;
                score++;
                if(score == brickRowCount*brickColumnCount) {
                  alert("YOU WIN, CONGRATULATIONS!");
                  document.location.reload();
                }
              }
            }
          }
        }
      

Screenshot 8

Screenshot 8

Exercise 8

Add more points per brick hit, print out the number of collected points in the end game alert box.

Source code 8

9. Mouse controls

Mouse movement listener

        document.addEventListener("mousemove", mouseMoveHandler, false);
      

Mouse movement handler

        function mouseMoveHandler(e) {
          var relativeX = e.clientX - canvas.offsetLeft;
          if(relativeX > 0 && relativeX < canvas.width) {
            paddleX = relativeX - paddleWidth/2;
          }
        }
      

Screenshot 9

Screenshot 9

Exercise 9

Adjust the boundaries of the paddle movement, so the whole paddle will be visible on both edges of the Canvas instead of only half of it.

Source code 9

10. Finishing up

Lives

        var lives = 3;
      

Draw lives

        function drawLives() {
          ctx.font = "16px Arial";
          ctx.fillStyle = "#0095DD";
          ctx.fillText("Lives: "+lives, canvas.width-65, 20);
        }
      

Removing life instead of game over

        lives--;
        if(!lives) {
          alert("GAME OVER");
          document.location.reload();
        }
        else {
          x = canvas.width/2;
          y = canvas.height-30;
          dx = 2;
          dy = -2;
          paddleX = (canvas.width-paddleWidth)/2;
        }
      

Print it out

        function draw() {
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          drawBricks();
          drawBall();
          drawPaddle();
          collisionDetection();
          drawScore();
          drawLives();
          // ...
        }
      

Request Animation Frame

setInterval(draw, 10); => draw();

requestAnimationFrame(draw);

Screenshot 10

Screenshot 10

Exercise 10

Change the number of lives and the angle the ball bounces off the paddle.

Source code 10

Thanks! Questions?

  • {Your name and details}
  • Slides: {slideurl}