How to make a Racing Car using Box2D in Javascript

By | January 13, 2023

Racing car in box2d

Box2d is a physics 2d simulation library and is used in game development. Apart from C/C++ it has ports for various languages like python, javascript etc. With the availability of the html5 canvas element and many other html5 apis, it becomes possible to write browser based games in javascript. So in this article we shall try to make a simple racing car kind of element in javascript using the box2d library.

Here we use the Box2d Javascript port from

http://code.google.com/p/box2dweb/

Car

The car shown here has 5 parts :

1. Middle Body
2. 2 Front wheels connected to body using revolute joints.
3. 2 Rear wheels connected to body using prismatic joints with limits

A much more simpler approach would be to take just 1 piece of shape and move it leftward or rightward. But that would not look very 'natural' or realistic as the one with wheels and extra simulation would.

In terms of physics, to move the car, force is applied on the front wheels in the direction of their rotation angle, means push them in whichever direction they are facing. So when the front wheels move, they take the rest of the car with them making the whole car looks very real.

Code

The World

Box2d has a 'world' where physical 'objects' would exist. Here are some rules of physics for our world.

1. Our world has no gravity, otherwise things would start falling down. This is done to create an impression of moving freely in all directions.

2. The car has some linear damping and angular damping, to prevent it from moving indefinitely as if it was in free space.

We use javascript , and hence the HTML5 canvas tag to draw things. Its quite easy as we would see in few lines from here. Also Jquery is used for some simple things like selection of dom elements etc.

html

The html code is minimal, just get in the javascript libraries and add a canvas element.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<title>Racing Car in Box2d</title>
	
	<script src="jquery-1.6.4.min.js"></script>
	<script src="Box2dWeb-2.1.a.3.js"></script>
	<script src="code.js"></script>
</head>

<body style="margin:0px;">
	<canvas id="canvas" width='640' height='480' style="background-color:#333;"></canvas>
</body>

</html>

The above html file is the page to be opened in browser to see the car in action.

Javascript

The javascript code would be :

/*
	Racing car example
	Silver Moon ([email protected])
*/

//Get the objects of Box2d Library
var b2Vec2 = Box2D.Common.Math.b2Vec2
	,  	b2AABB = Box2D.Collision.b2AABB
	,	b2BodyDef = Box2D.Dynamics.b2BodyDef
	,	b2Body = Box2D.Dynamics.b2Body
	,	b2FixtureDef = Box2D.Dynamics.b2FixtureDef
	,	b2Fixture = Box2D.Dynamics.b2Fixture
	,	b2World = Box2D.Dynamics.b2World
	,	b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
	,	b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
	,	b2DebugDraw = Box2D.Dynamics.b2DebugDraw
	,  	b2MouseJointDef =  Box2D.Dynamics.Joints.b2MouseJointDef
	,  	b2Shape = Box2D.Collision.Shapes.b2Shape
	,	b2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef
	,	b2Joint = Box2D.Dynamics.Joints.b2Joint
	,	b2PrismaticJointDef = Box2D.Dynamics.Joints.b2PrismaticJointDef
	;
         
var game = {
	
	'key_down' : function(e)
	{
		var code = e.keyCode;
		
		//left
		if(code == 37)
		{
			steering_angle = max_steer_angle;
			steer_speed = 1.0;
		}
		//up
		if(code == 38)
		{
			car.gear = 1;
			car.start_engine();
		}
		
		//right
		if(code == 39)
		{
			steering_angle = -1 * max_steer_angle;
			steer_speed = 1.0;
		}
		
		//down
		if(code == 40)
		{
			car.gear = -1;
			car.start_engine();
		}
	} ,
	
	'key_up' : function(e)
	{
		var code = e.keyCode;
		
		//stop forward velocity only when up or down key is released
		if(code == 38 || code == 40)
		{
			car.stop_engine();
		}
		//LEFT OR RIGHT
		if(code == 37 || code == 39)
		{
			steering_angle = 0.0;
			//This is called POWER STEERING, when the steering is released the front wheels need to become straight very quickly
			steer_speed = 8.0;
		}
	} ,
	
	'screen_width' : 0 ,
	'screen_height' : 0 ,
};

var engine_speed = 0;
var steering_angle = 0;
var steer_speed = 1.0;
var max_steer_angle = Math.PI/3;	//60 degrees to be precise
var world;
var ctx;
var canvas_height;

//1 metre of box2d length becomes 100 pixels on canvas
var scale = 100;

//The car object
var car = {
	
	'top_engine_speed' : 2.5 ,
	'engine_on' : false ,
	
	'start_engine' : function()
	{
		car.engine_on = true;
		car.engine_speed = car.gear * car.top_engine_speed;
	} ,
	
	'stop_engine' : function()
	{
		car.engine_on = false;
		car.engine_speed = 0;
	} ,
	
	'gear' : 1
};

/*
	Draw a world
	this method is called in a loop to redraw the world
*/	 
function redraw_world(world, context) 
{
	//convert the canvas coordinate directions to cartesian
	ctx.save();
	ctx.translate(0 , canvas_height);
	ctx.scale(1 , -1);
	world.DrawDebugData();
	ctx.restore();
	
	ctx.font = 'bold 15px arial';
	ctx.textAlign = 'center';
	ctx.fillStyle = '#ffffff';
	ctx.fillText('Use arrow keys to move the car', canvas_width/2, 20);
	ctx.fillText('Car Gear : ' + car.gear + ' Car Engine Speed : ' + car.engine_speed + ' mps ', canvas_width/2, 40);
}

//Create box2d world object
function createWorld() 
{
	var gravity = new b2Vec2(0, 0);
	var doSleep = true;
	
	world = new b2World(gravity , doSleep);
	
	//setup debug draw
	var debugDraw = new b2DebugDraw();
	debugDraw.SetSprite(document.getElementById("canvas").getContext("2d"));
	debugDraw.SetDrawScale(scale);
	debugDraw.SetFillAlpha(0.5);
	debugDraw.SetLineThickness(1.0);
	debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
	
	world.SetDebugDraw(debugDraw);
	
	createBox(world , game.screen_width / 2 , 0.5 , game.screen_width/2 - 1 , 0.1 , { 'type' : b2Body.b2_staticBody , 'restitution' : 0.5 });
	createBox(world , game.screen_width -1  , game.screen_height / 2 , 0.1 , game.screen_height/2 -1 , { 'type' : b2Body.b2_staticBody , 'restitution' : 0.5 });
	
	//few lightweight boxes
	var free = {'restitution' : 1.0 , 'linearDamping' : 1.0 , 'angularDamping' : 1.0 , 'density' : 0.2};
	createBox(world , 2 , 2 , 0.5 , 0.5 , free);
	createBox(world , 5 , 2 , 0.5 , 0.5 , free);
	
	return world;
}		

//Create standard boxes of given height , width at x,y
function createBox(world, x, y, width, height, options) 
{
	 //default setting
	options = $.extend(true, {
		'density' : 1.0 ,
		'friction' : 0.0 ,
		'restitution' : 0.2 ,
		
		'linearDamping' : 0.0 ,
		'angularDamping' : 0.0 ,
		
		'gravityScale' : 1.0 ,
		'type' : b2Body.b2_dynamicBody
	}, options);
	
	var body_def = new b2BodyDef();
	var fix_def = new b2FixtureDef;
	
	fix_def.density = options.density;
	fix_def.friction = options.friction;
	fix_def.restitution = options.restitution;
	
	fix_def.shape = new b2PolygonShape();
	
	fix_def.shape.SetAsBox( width , height );
	
	body_def.position.Set(x , y);
	
	body_def.linearDamping = options.linearDamping;
	body_def.angularDamping = options.angularDamping;
	
	body_def.type = options.type;
	
	var b = world.CreateBody( body_def );
	var f = b.CreateFixture(fix_def);
	
	return b;
}

/*
	This method will draw the world again and again
	called by settimeout , self looped ,
	game_loop
*/
function game_loop() 
{
	var fps = 60;
	var time_step = 1.0/fps;
	
	update_car();
	//move the world ahead , step ahead man!!
	world.Step(time_step , 8 , 3);
	//Clear the forces , Box2d 2.1a	
	world.ClearForces();
	
	//redraw the world
	redraw_world(world , ctx);
	
	//call this function again after 10 seconds
	setTimeout('game_loop()', 1000/60);
}


// main entry point
$(function() 
{
	game.ctx = ctx = $('#canvas').get(0).getContext('2d');
	var canvas = $('#canvas');
	
	game.canvas_width = canvas_width = parseInt(canvas.width());
	game.canvas_height = canvas_height = parseInt(canvas.height());
	
	game.screen_width = game.canvas_width / scale;
	game.screen_height = game.canvas_height / scale;
	
	//first create the world
	world = createWorld();
	
	create_car();
	
	$(document).keydown(function(e)
	{
		game.key_down(e);
		return false;
	});
	
	$(document).keyup(function(e)
	{
		game.key_up(e);
		return false;
	});
	
	//Start the Game Loop!!!!!!!
	game_loop();
});

function create_car()
{
	car_pos = new b2Vec2(3 , 3);
	car_dim = new b2Vec2(0.2 , 0.35);
	car.body = createBox(world , car_pos.x , car_pos.y , car_dim.x , car_dim.y , {'linearDamping' : 10.0 , 'angularDamping' : 10.0});
	
	var wheel_dim = car_dim.Copy();
	wheel_dim.Multiply(0.2);
	
	//front wheels
	left_wheel = createBox(world , car_pos.x - car_dim.x , car_pos.y + car_dim.y / 2 , wheel_dim.x , wheel_dim.y , {});
	right_wheel = createBox(world , car_pos.x + car_dim.x, car_pos.y + car_dim.y / 2 , wheel_dim.x , wheel_dim.y , {});
	
	//rear wheels
	left_rear_wheel = createBox(world , car_pos.x - car_dim.x , car_pos.y - car_dim.y / 2 , wheel_dim.x , wheel_dim.y , {});
	right_rear_wheel = createBox(world , car_pos.x + car_dim.x, car_pos.y - car_dim.y / 2 , wheel_dim.x , wheel_dim.y , {});
	
	var front_wheels = {'left_wheel' : left_wheel , 'right_wheel' : right_wheel};
	
	for (var i in front_wheels)
	{
		var wheel = front_wheels[i];
		
		var joint_def = new b2RevoluteJointDef();
		joint_def.Initialize(car.body , wheel, wheel.GetWorldCenter());
		
		//after enablemotor , setmotorspeed is used to make the joins rotate , remember!
		joint_def.enableMotor = true;
		joint_def.maxMotorTorque = 100000;
		
		//this will prevent spinning of wheels when hit by something strong
		joint_def.enableLimit = true;
  		joint_def.lowerAngle =  -1 * max_steer_angle;
		joint_def.upperAngle =  max_steer_angle;
		
		//create and save the joint
		car[i + '_joint'] = world.CreateJoint(joint_def);
	}
	
	var rear_wheels = {'left_rear_wheel' : left_rear_wheel , 'right_rear_wheel' : right_rear_wheel};
	
	for (var i in rear_wheels)
	{
		var wheel = rear_wheels[i];
		
		var joint_def = new b2PrismaticJointDef();
		joint_def.Initialize( car.body , wheel, wheel.GetWorldCenter(), new b2Vec2(1,0) );
	
		joint_def.enableLimit = true;
		joint_def.lowerTranslation = joint_def.upperTranslation = 0.0;
		
		car[i + '_joint'] = world.CreateJoint(joint_def);
	}
	
	car.left_wheel = left_wheel;
	car.right_wheel = right_wheel;
	car.left_rear_wheel = left_rear_wheel;
	car.right_rear_wheel = right_rear_wheel;
	
	return car;
}

//Method to update the car
function update_car()
{
	var wheels = ['left' , 'right'];
	
	//Driving
	for(var i in wheels)
	{
		var d = wheels[i] + '_wheel';
		var wheel = car[d];
		
		//get the direction in which the wheel is pointing
		var direction = wheel.GetTransform().R.col2.Copy();
		//console.log(direction.y);
		direction.Multiply( car.engine_speed );
		
		//apply force in that direction
		wheel.ApplyForce( direction , wheel.GetPosition() );
	}	
	
	//Steering
	for(var i in wheels)
	{
		var d = wheels[i] + '_wheel_joint';
		var wheel_joint = car[d];
		
		//max speed - current speed , should be the motor speed , so when max speed reached , speed = 0;
		var angle_diff = steering_angle - wheel_joint.GetJointAngle();
		wheel_joint.SetMotorSpeed(angle_diff * steer_speed);
	}
}

Next you can try to put some graphics on the car body to make it look even more real. Put some grass below the car to make it look like its moving on a real surface etc.

Resources

For the Box2d API documentation check :
http://www.box2dflash.org/docs/

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].

12 Comments

How to make a Racing Car using Box2D in Javascript
  1. peter

    Hi, i copied function create_car() to my oop game, but wheel is rotating around body center not around won axis.

    1. Varun Upadhyay

      just make that shape fixture definition sensor = true; like this

      function createSensor(world, options)
      {
      options = $.extend(true, {
      ‘density’ : 1.0 ,
      ‘friction’ : 0.0 ,
      ‘restitution’ : 0.2 ,
      ‘linearDamping’ : 0.0 ,
      ‘angularDamping’ : 0.0 ,
      ‘gravityScale’ : 1.0 ,
      ‘type’ : b2Body.b2_dynamicBody
      }, options);

      var body_def = new b2BodyDef();
      var fix_def = new b2FixtureDef;
      fix_def.shape = new b2PolygonShape();
      var vertices = new Array();
      vertices[0] = new b2Vec2(3.24, 13.66);
      vertices[1] = new b2Vec2(6.34, 14.50);
      vertices[2] = new b2Vec2(7.50, 13.35);
      vertices[3] = new b2Vec2(4.13, 13.00);
      fix_def.shape.SetAsArray(vertices, 4);
      fix_def.isSensor = true; // <————————————————-
      world.CreateBody( body_def ).CreateFixture(fix_def);
      world.SetContactListener(listener);
      }
      var listener = new Box2D.Dynamics.b2ContactListener;
      listener.BeginContact = function(contact) {
      // console.log(contact.GetFixtureA().GetBody().GetUserData());
      }
      listener.EndContact = function(contact) {
      // console.log(contact.GetFixtureA().GetBody().GetUserData());
      }

    2. Silver Moon

      use sensors. when the car is over the sensor, slow down the max speed of the car.
      just set the isSensor property true on the fixture of the sensor shape. sensors will detect collision, but not collide physically.

    1. Varun Upadhyay

      i have resolved this problem. Just go in redraw_world function and write following code

      context.clearRect(0, 0, 500, 300);//Area of your canvas
      context.save();
      //maxY is the max position by car (extreme top) same with maxX(extreme right) in b2vec2 coordinate. “wd” is canvas width, “ht” is canvas height, zoomLevel 1 by default (canvas zoom actually)
      context.translate(((car.body.GetPosition().x*wd)/maxX)*zoomLevel,((ht)-(car.body.GetPosition().y*ht/19.18))*zoomLevel);

      context.rotate(-car.body.GetAngle());

      context.drawImage(carImage,-carImage.width/2, -carImage.height/2);//(((car.body.GetPosition().x*wd)/maxX)-carImage.width/2),((ht)-(car.body.GetPosition().y*ht/19.18)-carImage.height/2));

      context.restore();

Leave a Reply

Your email address will not be published. Required fields are marked *