Making A Block It Game Prototype With HTML5
In this tutorial we are creating a protoype of a game like the mobile game Block It from Ketchapp Games. Block It is an action game that requires fast reflexes from the player. The game consist of a moving ball that is enclosed in a room with three walls. When the player touches the screen, a fourth wall appears. The goal of the game is to block the ball from moving out of the room. The player can prevent the ball from escaping the room by touching the screen at the right moment, because when the ball collides with a wall, the ball is bounced back. Every time the player successfully blocks the ball, the speed of the ball increases. As the game progresses, it becomes harder and harder to keep the ball from escaping the room.
Making a game with the mechanics of Block It is pretty easy. In my tutorial How To Make An HTML5 Canvas Game we have already seen how to make a game with a bouncing square inside a room. The game we are making in this tutorial is only a bit more complicated, but most of the code it pretty similar.
Click here to go directly to the end of this article and play the game.
Creating The Game
We are using the Basic HTML5 Game Framework from a previous tutorial to quickly create this prototype. The game doesn’t have a lot of elements. It has a level which consists of 4 walls, a score and a bouncing ball. The only user-interaction that we need to make is detecting clicks or touch actions which results in showing the bottom wall.
We start by defining the level, the ball and a couple of variables. The level, a square room, has a position and a size. It also defines the thickness of the walls using wallsize. We will use this value later on when we are drawing the walls and when checking for collisions.
1 2 3 4 5 6 7 8 | // Level properties var level = { x: 80, y: 48, width: canvas.width - 160, height: canvas.height - 96, wallsize: 16 }; |
The ball has a position and a size. In our code, the ball is actually a square. This helps us with collision detection. Of course, when we draw the ball, we draw the shape of a circle. The ball also needs a direction and a speed, because the ball is moving somewhere inside the level. The direction has two components, xdir and ydir. At each animation frame, these values are added to the position of the ball as we will see.
1 2 3 4 5 6 7 8 9 10 | // Ball var ball = { x: 0, y: 0, width: 35, height: 35, xdir: 0, ydir: 0, speed: 0 } |
The following variables are used in our calculations and interactions. Read the comments to see what they stand for.
1 2 3 4 5 6 7 8 9 | // Variables var score = 0; // Score var blocked = false; // Bottom wall is active var blockedtime = 0; // How long the wall has been active var blockedlength = 0.1; // How long the wall will be active var blockcooldown = 1; // Waiting time between wall activations var gameover = true; // Game is over var gameovertime = 0; // How long we have been game over var gameoverdelay = 1; // Waiting time after game over |
Starting A New Game
When we start a new game, we need to initialize our variables to a default state. We initialize the ball by generating a random direction within certain limits by using the Math.random() function. You could say a direction vector is formed by combining the xdir and ydir variables of the ball. We want the direction vector to have a length of exactly 1, so we need to normalize this vector by calculating the length of the vector and dividing xdir and ydir by this length.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | function newGame() { // Initialize the ball ball.x = level.x + (level.width - ball.width) / 2; ball.y = level.y + level.height - ball.height; ball.speed = 500; // Random direction ball.xdir = 0.4 + (Math.random())/2; ball.ydir = -1; // Random left or right if (Math.random() < 0.5) { ball.xdir *= -1; } // Normalize the direction var dirlen = Math.sqrt(ball.xdir*ball.xdir + ball.ydir*ball.ydir); ball.xdir /= dirlen; ball.ydir /= dirlen; // Initialize the score score = 0; // Initialize variables blocked = false; blockedtime = blockcooldown; gameover = true; gameovertime = 0; } |
Game Logic
The code below is the main game logic. What follows is a short overview of the code. The ball is moved in the direction of the xdir and ydir as we have seen before, with a certain speed. Next, if the wall is closed by the player, it needs to be opened after a certain time. Collisions are checked between the 3 static walls of the level and the ball. Finally, collisions between the closed bottom wall and the ball are checked. After every collision, the direction of the ball is reversed. If the ball escaped the level, the game is over. Check out the comments in the code below for more information.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | function updateGame(dt) { // Move the ball, time-based ball.x += dt * ball.speed * ball.xdir; ball.y += dt * ball.speed * ball.ydir; // Open the wall after some time blockedtime += dt; if (blocked) { if (blockedtime > blockedlength) { blocked = false; } } // Handle left and right collisions with the level if (ball.x <= level.x) { // Left edge ball.xdir *= -1; ball.x = level.x; } else if (ball.x + ball.width >= level.x + level.width) { // Right edge ball.xdir *= -1; ball.x = level.x + level.width - ball.width; } // Handle top collisions with the level if (ball.y <= level.y) { // Top edge ball.ydir *= -1; ball.y = level.y; } // Check if the ball collides with the bottom wall // Only do this, while the ball is moving to the bottom if (blocked && ball.ydir > 0 && rectIntersection(ball.x, ball.y, ball.width, ball.height, level.x, level.y+level.height, level.width, level.wallsize) && ball.ydir > 0) { // Wall is closed ball.ydir *= -1; // Increase the score score += 1; // Increase the speed of the ball by 5 percent ball.speed *= 1.05; } else if (ball.ydir > 0 && ball.y > level.y+level.height+level.wallsize) { // Ball escaped, game over ball.speed = 0; gameover = true; } } // Check if there is a rect intersection function rectIntersection(x1, y1, w1, h1, x2, y2, w2, h2) { if (x1 <= x2 + w2 && x1 + w1 >= x2 && y1 <= y2 + h2 && y1 + h1 >= y2) { return true; } return false; } |
This is our user interaction logic. When the player clicks the mouse button, we show the wall for some time.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // Mouse event handlers function onMouseDown(e) { // Get the mouse position var pos = getMousePos(canvas, e); // Start a new game if (gameover && gameovertime > gameoverdelay) { newGame(); gameover = false; } // Show the wall if (!gameover && blockedtime >= blockcooldown) { blockedtime = 0; blocked = true; } } |
Drawing The Game
The previous chapter showed the game logic. Now we have to present the game to the player by drawing all of the elements to the screen. We need to draw the walls, the score and the ball to the screen. For this protoype, we are simply using the HTML5 Canvas fillRect function to draw walls and we use the arc function to draw the circle of the ball. To draw the score, we use the canvas text drawing function fillText.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | // Render the game function render() { // Draw background context.fillStyle = "#577ddb"; context.fillRect(0, 0, canvas.width, canvas.height); // Draw walls context.fillStyle = "#ffffff"; context.fillRect(level.x-level.wallsize, level.y-level.wallsize, level.wallsize, level.height+2*level.wallsize); // Left context.fillRect(level.x+level.width, level.y-level.wallsize, level.wallsize, level.height+2*level.wallsize); // Right context.fillRect(level.x, level.y-level.wallsize, level.width, level.wallsize); // Top // Draw bottom wall if closed if (blocked) { context.fillRect(level.x, level.y+level.height, level.width, level.wallsize); } // Draw score inside the level context.fillStyle = "rgba(255, 255, 255, 0.7)"; context.font = "240px Verdana"; drawCenterText(score, level.x, level.y+level.height-150, level.width); // Draw the ball var centerx = ball.x + ball.width/2; var centery = ball.y + ball.height/2; context.beginPath(); context.arc(centerx, centery, ball.width/2-3, 0, 2*Math.PI, false); context.fillStyle = "#000000"; context.fill(); context.lineWidth = 5; context.strokeStyle = "#ffffff"; context.stroke(); // Game over if (gameover) { context.fillStyle = "rgba(0, 0, 0, 0.5)"; context.fillRect(0, 0, canvas.width, canvas.height); context.fillStyle = "#ffffff"; context.font = "24px Verdana"; drawCenterText("Click to start", 0, canvas.height/2, canvas.width); } } // Draw text that is centered function drawCenterText(text, x, y, width) { var textdim = context.measureText(text); context.fillText(text, x + (width-textdim.width)/2, y); } |
Block It Prototype Example
Here is our finished prototype of a game like Block It. Click the screen to start the game. Don’t let the ball escape the room, by clicking the screen at the correct moment to make the bottom wall appear. The full source code is available on GitHub, licensed under GPLv3.