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/
Hi, i copied function create_car() to my oop game, but wheel is rotating around body center not around won axis.
how can we use wheel.GetTransform().R.col2.Copy() (line 332) in Xcode? please help
use .R -> .q, .R.col1 -> .q.GetXAxis() and .R.col2 became .q.GetYAxis()
how to add shapes, where Car goes over that and Car speed will become slow?
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());
}
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.
I am having a great trouble in adding an image to the car body.Please help
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();
Awesome , so realistic simulation. Thanks a lot.
Wow amazing what you created there.
Currently i am trying to make something similar in cocos2d.
Helped me, thx.
Did you get any help in xCode? please let me know, if you get the code for xCode
i have created this for xCode, now. See this
http://varunxcode.blogspot.in/2013/04/xcode-car-race-with-box2d.html