Make a racing car using Box2d in javascript
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.
Demo
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 (m00n.silv3r@gmail.com)
*/
//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/
Related Posts
-
http://indianparsel.info/ Varun Upadhyay
-
http://indianparsel.info/ Varun Upadhyay
-
-
http://indianparsel.info/ Varun Upadhyay
-
http://indianparsel.info/ Varun Upadhyay
-
http://www.binarytides.com/ Silver Moon
-
-
edd
-
http://indianparsel.info/ Varun Upadhyay
-
-
http://salimsazzad.wordpress.com sazzadcsedu
-
http://www.devno.com Roman
-
http://indianparsel.info/ Varun Upadhyay
-
http://indianparsel.info/ Varun Upadhyay
-