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,
dist, and open
index.html in your browser.
git clone email@example.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.
To get started, will we need to acquire our needed dependencies:
- PhysicsJS - Models the world gravity and all of the objects’ mass, velocity, surface friction, and collisions inside the game universe
- FunctionalJS - Useful for mapping, reducing, and filtering objects as well as currying, composing, and creating anonymous functions
- EaselJS - Renders all of the onscreen elements to our canvas
- PubSubJS - Provides asynchronous message passing between the components
We will also need NVM. Make sure to install a version of Node
Dubul Rubul consists of multiple components that handle the various functionalities of the game.
application.js- All of the initialization, update, and reset logic
ball.js- The ball model keeping track of the physics, dimensions, and the color
block.js- Similar to the ball model but for the blocks onscreen
canvas.js- All of the draw and physics logic governing over the
canvasElement.js- The parent model which
computerPaddleControls.js- The logic controlling the computer’s paddle seen to the left
init.js- Creates a new
Applicationand hides the title screen
paddle.js- The paddle model keeping track of the paddle’s physics, shape, and color
paddleControls.js- The parent object to
playerPaddleControls.js- The logic monitoring the player’s input and updating their onscreen paddle
referee.js- Handles the business logic of what actions receive what points
scoreBoard.js- Updates and resets the onscreen scores displayed at the top of the screen
update.js- The main game loop
util.js- Various helper methods DRY-ing up the code base
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 ball has two constants:
WIDTH. It has a
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.
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.
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.
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
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
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.
Canvas object has two main concerns. The first is abstracting the adding, updating, and removal of the game objects for both
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
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.
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.
The paddle controls allow the player and computer to move their paddle up or down and to rotate it in place.
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.
Most games have some scoring mechanism and Dubul Rubul is no different.
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 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.
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.