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:
- 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 >= v4.0.0
.
Components
Dubul Rubul consists of multiple components that handle the various functionalities of the game.
application.js
- All of the initialization, update, and reset logicball.js
- The ball model keeping track of the physics, dimensions, and the colorblock.js
- Similar to the ball model but for the blocks onscreencanvas.js
- All of the draw and physics logic governing over theCanvasElement
s onscreencanvasElement.js
- The parent model whichBall
,Block
, andPaddle
inherit fromcomputerPaddleControls.js
- The logic controlling the computer’s paddle seen to the leftinit.js
- Creates a newApplication
and hides the title screenpaddle.js
- The paddle model keeping track of the paddle’s physics, shape, and colorpaddleControls.js
- The parent object toComputerPaddleControls
andPlayerPaddleControls
playerPaddleControls.js
- The logic monitoring the player’s input and updating their onscreen paddlereferee.js
- Handles the business logic of what actions receive what pointsscoreBoard.js
- Updates and resets the onscreen scores displayed at the top of the screenupdate.js
- The main game looputil.js
- Various helper methods DRY-ing up the code base
Game Arena
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 canvasElement
s 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.
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 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 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 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 canvasElement
s 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.
Recap
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.