Make a HTML5 Canvas Game with Physics

2016-05-12  

David Lettier  

Dubul Rubul

The game we will be building is inspired by Breakout. There are major differences. The blocks move realistically and the player competes opposite side of the computer to smash the most blocks. Certain blocks give more points then others and hitting the walls or the other player’s ball gives a point the other side.

The entire project can be found on GitHub and a playable version is hosted on Lettier.com. If for any reason the hosted version is down, you can clone the repository, cd into dist, and open index.html in your browser.

git clone git@github.com:lettier/dubulrubul.git
cd dubulrubul/dist
xdg-open index.html # Or open on Mac OS X.

If you find the game enjoyable, feel free to the project on GitHub.

Dependencies

To get started, will we need to acquire our needed dependencies:

We will also need NVM. Make sure to install a version of Node >= v4.0.0.

Components

Dubul Rubul consists of multiple components that handle the various functionalities of the game.

Game Arena

The game arena with scoreboard.

The game arena with scoreboard.

We will place two document divisions at the top of the screen to display the computer’s and player’s scores. The other parts of the arena are the two canvases. The first canvas is used to render/draw/view the physics calculations. This is useful for debugging but is not normally shown. The second canvas displays all of the canvasElements or the game’s entities. Upon initialization, the canvases are re-sized to fit the entire screen. If the window is ever re-sized, the game arena’s size is updated to match.

The physics canvas.

The physics canvas.

Canvas Elements

The game consists of three models: the ball, the block, and the paddle. To avoid duplication of common properties, they all inherit from the CanvasElement object.

Ball

The ball has two constants: HEIGHT and WIDTH. It has a x and y 2D position in pixels and a rotation in radians. Since the ball is uniform in shape and color, the rotation cannot be seen unless the physics are drawn.

Block

The block is similar to the ball but defines logic around prizes. Prizes are blocks that are colored corresponding to either the left or right side. If one side smashes a prize block with its color, it gets extra points. If another side smashes a prize block for the opposite side, the opposite gets the extra points. The goal for the player is to avoid the computer’s prize blocks, hit its own prize blocks, and hit as many neutral gray blocks as they can.

Paddle

The paddles are used to hit the ball back and forth across the arena. They can spin, helping to aim or deflect the ball where the player or computer would like them to go. To spin them, the player moves their mouse over the screen from left to right.

Initialization

Everything starts with the init function which is called once the player presses the start button. This creates a new application and calls its init function. Once the application initializes, it hides the title overlay.

The application initialization setups all of the game components. It creates the canvas, paddles, balls, and blocks. For each object created, it calls the object’s init function.

The dynamic block grid.

The dynamic block grid.

The blocks are dynamically created. They stretch from the top to bottom of the screen and take up about 15% of the center screen real estate. Once they are hit by the balls, the blocks topple over.

With all of the objects setup, the application wires up all of the asynchronous events and PubSubJS messages. When another object publishes the game over message, it calls the reset function. The major events it listens for are resize and keyup.

The Game Loop

The game loop.

The game loop.

The game loop is repeatedly called until a round is over. It is responsible for getting the graphics and physics to update with each new requested animation frame.

We would like the game animation to play at roughly the same speed no matter what hardware the game is running on. We’ll use Time-based Animation to keep the animation fairly consistent across different machines. To do this, we calculate how long it took to get back to updating the game loop. By passing this delta scalar into canvas.update, any position or rotation update can be scaled by how long it took since delta was last calculated.

If it is taking no time at all to get back to the update function, the changes in position or rotation will be small. However, if it is taking forever to call update again and again, each change will be scaled up immensely. Another way to think about it is a flip book. Picture two flip books where each is an animation of a person running from the left to the right. For the fast flippers, you would give them the flip book with more pages where for each page, the person is updated ever so slightly. For the slow flippers, you would give them the flip book with less pages where each page updates the person at a greater clip. The fast flippers have more pages to get through while the slow flippers have less. If both start at the same time, the animated person running between the two flip books will match.

The two flip books.

The two flip books.

The Canvas

The Canvas object has two main concerns. The first is abstracting the adding, updating, and removal of the game objects for both EaselJS and PhysicsJS. The second major concern is keeping the Stage and world objects in sync.

For every iteration of update, the Canvas performs three steps. Some canvasElements are not fully controlled by the physics simulation. For these objects, their x-y coordinates and/or rotation are updated via their world body object. After updating the physics simulation, the world is allowed to advanced one step. This updates all of the physical bodies allowing us to update all of the Stage objects ultimately resulting in the animation you see in the game. With everything updated, we can begin the render process that draws the shapes on the screen.

The other Canvas concerns are listening for and publishing certain events. These events are consumed or broadcast using PubSubJS. The events listened for are to update the paddles or blocks and to shake the canvas. When received, the HTML canvas is moved up and down every so slightly to give the illusion of the game arena being rattled. The broadcast messages are for the various types of collisions. These will be consumed by the Referee which in turn will determine the computer’s or player’s score update.

Paddle Controls

The paddle controls allow the player and computer to move their paddle up or down and to rotate it in place.

Computer

This is where we define the AI for the computer paddle. For now the logic is extremely basic–it merely follows the player’s ball. In a later post, we will revisit with a machine learning approach.

Player

The player can rotate their paddle by moving their mouse from side to side. Moving the mouse up or down will also move their paddle up or down.

Scoring

Most games have some scoring mechanism and Dubul Rubul is no different.

Scoreboard

The Scoreboard object maintains both the green and blue div bars at the top of the screen as well as the player’s and computer’s onscreen score counts. Typically the Referee will publish a updateScore message which will be consumed by the Scoreboard object. Once it receives this message, it updates either the computer’s or player’s current score.

Referee

The Referee object handles most of the Business Logic or gameplay mechanics. It listens for the noMoreBlocks message. Once received, it will publish the gameOver message. This gameOver message will be picked up by the Application object, resetting the game.

The blockBallHit collision, published by the Canvas object, translates into a point scheme. When the player loses points to the computer, the Referee publishes the shakeCanvas message. This provides a visual cue to the player that they are missing out on points.

The computer or player can lose points to the opposite side if they touch the other side’s ball with their own paddle. If the ball touches the bounding box, the opposite side earns a point.

Wrap-up

Using functional programming, a HTML5 canvas, a physics simulation, and a publish subscribe message model, we built Dubul Rubul–a video game playable in any modern browser. In future posts we will revisit the computer paddle’s AI and look at adding sound effects for added immersion.