Make a simple html5 game with box2d in javascript – tutorial

By | January 13, 2023

Demo

First lets play the game that we shall be making in this tutorial. Its called Fruit Hunter. Tested on Chrome and Firefox.

Click in the game then start using the arrow keys to control the monkey. Press the left, right and up arrow keys to make the monkey move and jump. Do not fall off the stands and try to collect as many apples as possible. More apples you collect, more the points.

That is a simple game and in this tutorial we shall learn how to make it. Its made in less than 500 lines of code. Uses box2d and jquery.

Pre-requisites

You should already be knowing how to write object oriented code in javascript and handle closures. Some basic knowledge of 2d graphs and geometry is also required.

Theory

Html5 and canvas

This is a browser based game that does not use other things like flash. It is code purely in javascript and draws the elements on the canvas element. The canvas element allows to draw things pixel by pixel making it possible to draw any kind of shapes etc. It also have functions to draw images, scale them etc.

Box2d World

Box2d is used to create a simulated environment (in short an animation) based on the rules of physics. Inside this environment/world box2d objects exist and interact according to the laws of physics (yes the same laws taught in school). So the game is essentially a collection of box2d objects that are interacting with each other. For example a box falling in gravity or a box resting on another immovable box. Box2d makes physics simulation and collision detection very easy, and these are the most important parts of any game.

I would suggest to read the basics of box2d in javascript if you are not already familiar with it.

Box2d and the canvas viewport

Recall 2d graphs and the cartesian coordinate system. Box2d works on a 2d world in cartesian coordinates where the x axis increases rightward and y axis increases upward. However for the canvas element the y axis increases downward.

So if the top left corner of the canvas is the origin (0,0) and an element is at (5,10) according to box2d (which is above the origin) and it is drawn the same way on the canvas at (5,10) then it would be drawn 10 steps below the origin or the opposite side. It would become a mirror image kind of thing.

So here comes the concept the viewport to do this correctly.

Imagine an infinitely sized cartesian coordinate graph on the wall behind your computer monitor. It is this graph that the real objects exist and are moving here and there.
Now the canvas is like a camera, rectangular in size that takes pictures from this graph and draws it onto itself. The camera too has a location on the real world graph. If the camera moves up, the objects would be drawn lower, if it moves right the objects are drawn leftward.

For simplicity sake, in this game the camera is fixed and does not move. Consider the lower left corner of the canvas/camera is the origin point of the box2d cartesian world. And lets assume that the height of the camera is 15 units. Check the image for better understanding.

Simple coordinate maths

So now if a point is at (5,12) in box2d world, the canvas coordinates can be calculated as follows

c_x = 5 - 0 = 5
c_y = 15 - 12 = 3

The black grid is the box2d coordinates and the red box is our canvas. Note that the y coordinate of an object is different in the 2 coordinate systems. The x coordinate has same values.

The function that does the conversion is

game.prototype.get_offset = function(vector)
{
	return new b2Vec2(vector.x - 0, this.screen_height - vector.y);
}

The point coordinates are in the variable named vector and the function returns another object of type b2Vec2 with the new coordinates.

Box2d dimensions

Box2d works with the unit of metre, whereas canvas uses pixels. Therefore there has to be some kind of conversion scale that can convert the dimensions of the box2d objects to pixels for drawing. The scale can be set to any constant like 30 50 or 80. However this has certain drawbacks. On smaller resolution displays the object would not scale accordingly.

Lets take an example. Scale is set to 50. So an object 1 metre in height would take 50 pixels. If resolution is 700 pixels in height then there can be 14 objects visible, and if the resolution is 200px in height 4 objects would be visible. And it might so happen that essential elements might go missing. An alternative is to have flexible scaling.

Variable scale means that either the height or the width is fixed. The following code shows how the scale is setup in the game

game.prototype.resize = function()
{
	var canvas = this.canvas;
	
	//Set the canvas dimensions to match the window dimensions
	var w = $(window).outerWidth();
	var h = $(window).outerHeight();
	
	canvas.width(w);
	canvas.height(h);
	
	canvas.attr('width' , w * 0.75);
	canvas.attr('height' , h * 0.75);
	
	this.canvas_width = canvas.attr('width');
	this.canvas_height = canvas.attr('height');
	
	this.screen_height = 10;
	this.scale = this.canvas_height / this.screen_height;
	this.screen_width = this.canvas_width / this.scale;
}

screen_height and screen_width are the screen dimensions in metres. The screen_height is directly setup to 10. This means that the height of the screen must be 10 metres always and that objects should grow small or big to fit.

So scale can be calculated like this
scale = canvas height in pixels / screen height;

or scale * screen height = canvas height in pixels

So the result of flexible scaling is that the game fits well in different sizes of display.

Code

The Game Object

The game object is responsible for the following tasks

1. Create objects and store them.
2. Keep simulating them in the tick loop.
3. Handle user input - keyboard and mouse to change elements in the simulation.
4. Handle other conditions and events like collision and modify the objects accordingly.

Inside the tick

The tick function is the function that does the simulation. It does this in 3 steps.

1. Tick each individual object inside the game once.
2. Make box2d move ahead by a certain time interval.
3. Draw everything on the canvas again.

So the logic is to simulate - draw - simulate - draw - simulate - draw ........ an infinite loop.

Lets take a look at the tick function

game.prototype.tick = function()
{
	if(!this.is_paused && this.on)
	{
		this.time_elapsed += 1;
		
		//create a random fruit on top
		if(this.time_elapsed % 50 == 0)
		{
			var xc = Math.random() * 8 + this.screen_width/2 - 4;
			var yc = this.screen_height/2 + 2.5;
			
			this.game_objects.push(new apple({x : xc ,y : yc,game:this}));
		}
		
		//tick all objects, if dead then remove
		for(var i in this.game_objects)
		{
			if(this.game_objects[i].dead == true)
			{
				delete this.game_objects[i];
				continue;
			}
			
			this.game_objects[i].tick();
		}
		
		//garbage collect dead things
		this.perform_destroy();
		
		//Step the box2d engine ahead
		this.box2d_world.Step(1/20 , 8 , 3);
		
		//important to clear forces, otherwise forces will keep applying
		this.box2d_world.ClearForces();
		
		//redraw the world
		this.redraw_world();
		
		if(!this.is_paused && this.on)
		{
			var that = this;
			//game.fps times in 1000 milliseconds or 1 second
			this.timer = setTimeout( function() { that.tick(); }  , 1000/this.fps);
		}
	}
}

The above tick function does the following things in this game

1. Create new apples every 50 ticks
2. Delete dead objects from the storage.
3. Tick individual objects.
4. Destroy objects scheduled for removal.
5. Simulate the box2d world ahead.
6. Redraw the world with new state.
7. Go to step 1.

That should be simple.

Objects - Apples, Monkey, Walls

The code is fully object oriented and all interactive elements on the screen are objects of some type. The objects in this game are :

1. Game object - Manages the game loop. Only 1 instance of this object is created.
2. Apple - Apples falling from tree. Multiple instances of this object, since so many apples are falling
3. Player - Player object. Only 1 instance of this object is created.
4. Wall - The wall slabs. Multiple instances

Each object has its own properties. For example wall objects are static objects, so they do not move or fall down under the force of gravity. Apple and player objects are dynamic objects, they fall downwards due to gravity.

Every object definition has similar set of functions

1. constructor - This defines properties of the object and setup the box2d objects.
2. tick - the tick function is called every game tick. This allows the object to do some processing on itself.

3. draw - this function allows the object to draw itself on the screen. All objects draw themselves and the game object only calls the draw function on each.

4. destroy - destroys the box2d object associated with this and marks the object as dead. In the next game tick it is fully removed from the list of game objects.

Apple object

Lets take a look at the apple object.

//Apple object
function apple(options)
{
	this.height = 0.25;
	this.width = 0.25;
	this.x = options.x;
	this.y = options.y;
	
	this.game = options.game;
	
	var linear_damping = 10 - (parseInt(this.game.points / 10) + 1)*0.5;
	
	var info = { 
		'density' : 10 ,
		'linearDamping' : linear_damping ,
		'fixedRotation' : true ,
		'userData' : this ,
		'type' : b2Body.b2_dynamicBody ,
	};
	
	var body = create_box(this.game.box2d_world , this.x, this.y, this.width, this.height, info);
	this.body = body;
}

The above is the constructor for the apple class. It takes an object as parameter which should contain the necessary information to create the apple. The x and y coordinates of the apple are needed. Dimensions are fixed to 0.25 metres of width and height. The create_box function is a helper function to create a box2d rectangle object of given dimension at a given position.

apple.img = img_res('apple.png');

apple.prototype.draw = function()
{
	if(this.body == null)
	{
		return false;
	}
	//draw_body(this.body, this.game.ctx);
	
	var c = this.game.get_offset(this.body.GetPosition());
	
	var scale = this.game.scale;
	
	var sx = c.x * scale;
	var sy = c.y * scale;
	
	var width = this.width * scale;
	var height = this.height * scale;
	
	this.game.ctx.translate(sx, sy);
	this.game.ctx.drawImage(apple.img , -width / 2, -height / 2, width, height);
	this.game.ctx.translate(-sx, -sy);
}

The draw function first finds out the position of the body in the box2d world using the GetPosition function. Then it draws the necessary image at that position. The draw function is directly called by the game object.

apple.prototype.tick = function()
{
	this.age++;
	
	//destroy the apple if it falls below the x axis
	if(this.body.GetPosition().y < 0)
	{
		this.game.destroy_object(this);
	}
}

The tick function is called on every game tick and can do a variety of tasks like checking if the object is dead or not or change its velocity for example.

//Destroy the apple when player eats it
apple.prototype.destroy = function()
{
	if(this.body == null)
	{
		return;
	}
	this.body.GetWorld().DestroyBody( this.body );
	this.body = null;
	this.dead = true;
}

The destroy function destroys the box2d object related to this apple and marks this instance as dead. Later on the game object will inspect it and remove it.

The other objects like player and wall have similar structures with few differences like

1. Wall object has a static box2d object which does not move under the force of gravity.
2. Player object has functions to change its velocity to left or right according to user input.

Collision detection

The collision detection is simple and the following is done.

1. If a player collides with an apple, then destroy the apple and give the player points.
2. If an apple collides with the wall just destroy it.

Notes

When coding such games using box2d it is important to keep in mind few points.

Never try to add/remove game objects inside callback functions like event handler and collision handler. Rather schedule the operation for the next game tick. The collision handlers are called while the Step function has not finished. Now modifying the world state in any manner before the Step (which is the simulation) completes can make the entire thing unpredictable.

For example if an apple collides with the the player it is not destroyed immediately in the collision handler function. It is just added to an array that is used in the next game tick to destroy objects.

Similary when the arrow keys are pressed, the velocity of the player object should not be modified right through the event handlers, instead a flag variable should be turned on, which will make the player move in the next game tick.

The box2d world should not be allowed to sleep

var doSleep = false;
var world = new b2World(gravity , doSleep);

doSleep is set to false. Otherwise dynamic objects hitting on static objects would become stationary and wont simulate.

Source

Download the source
https://www.binarytides.com/labs/fruit_hunter.zip

Further Ideas

The game made above is a very minimalistic game that could be made with an engine like box2d. There is a lot more that can be added to make it better and more creative. For example

1. Pause functionality.
2. A control menu, to start the game view high scores etc.
3. Add sound and music.
4. Preload resources like images etc.
5. Create new objects in the game, extend the concept.

To add a new item, just create the appropriate object and define its properties and behaviour in the collision handler.

For questions and feedback use the comment box below. Thanks

Resources and Credits

Monkey artwork taken from
http://www.vickiwenderlich.com/2011/06/game-art-pack-monkey-platformer/

Tree image from
http://www.lostgarden.com/2007/08/tree-scribble.html

Background image
http://www.mfgg.net/

About Silver Moon

A Tech Enthusiast, Blogger, Linux Fan and a Software Developer. Writes about Computer hardware, Linux and Open Source software and coding in Python, Php and Javascript. He can be reached at [email protected].

8 Comments

Make a simple html5 game with box2d in javascript – tutorial
  1. Taz

    You can also perform a “double-jump” :) (left | right) & up -> (left | right) & up. Not always happens tho.

  2. Dave

    It’s interesting that because you’re using box, you get some fun physics for free. The monkey can catch the walls if he falls down the hole, and jump his way back to safety.

    1. Silver Moon Post author

      yeah right, its because of friction that box2d applies to all bodies. that can be fixed with a little complex construction which i avoided to keep this article simple for beginners.

Comments are closed.