Step by step creation of a Box2D car/truck with motors and shocks

I am dealing with Box2D car for a long time, but I never published a tutorial about the making of a Box2D car, with working wheels and shocks.

If you want to see some posts about Box2D car you can have a look at two ways to make Box2D cars and create a terrain like the one in Tiny Wings with Flash and Box2D – Adding a car

We are going to build our car in 7 steps, so let’s start from the very beginning:

1 – World creation

In this basic step the environment to set up the simulation is created, and we also add the floor.

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	import Box2D.Dynamics.Joints.*;
	public class Main extends Sprite {
		private var world:b2World=new b2World(new b2Vec2(0,10),true);
		private var worldScale:int=30;
		public function Main():void {
			debugDraw();
			// ************************ THE FLOOR ************************ //
			// shape
			var floorShape:b2PolygonShape = new b2PolygonShape();
			floorShape.SetAsBox(640/worldScale,10/worldScale);
			// fixture
			var floorFixture:b2FixtureDef = new b2FixtureDef();
			floorFixture.density=0;
			floorFixture.friction=3;
			floorFixture.restitution=0;
			floorFixture.shape=floorShape;
			// body definition
			var floorBodyDef:b2BodyDef = new b2BodyDef();
			floorBodyDef.position.Set(320/worldScale,480/worldScale);
			// the floor itself
			var floor:b2Body=world.CreateBody(floorBodyDef);
			floor.CreateFixture(floorFixture);
			addEventListener(Event.ENTER_FRAME,updateWorld);
		}
		private function debugDraw():void {
			var worldDebugDraw:b2DebugDraw=new b2DebugDraw();
			var debugSprite:Sprite = new Sprite();
			addChild(debugSprite);
			worldDebugDraw.SetSprite(debugSprite);
			worldDebugDraw.SetDrawScale(worldScale);
			worldDebugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
			worldDebugDraw.SetFillAlpha(0.5);
			world.SetDebugDraw(worldDebugDraw);
		}
		private function updateWorld(e:Event):void {
			world.Step(1/30,10,10);
			world.ClearForces();
			world.DrawDebugData();
		}
	}
}

There’s nothing you did not already see in this blog, so I’d jump straight to the result…

… and then move on to next step.

2 – Adding the car body

At first, the car will be just a rectangle, so that’s what I am adding:

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	import Box2D.Dynamics.Joints.*;
	public class Main extends Sprite {
		private var world:b2World=new b2World(new b2Vec2(0,10),true);
		private var worldScale:int=30;
		private var car:b2Body;
		public function Main():void {
			debugDraw();
			// ************************ THE FLOOR ************************ //
			// shape
			var floorShape:b2PolygonShape = new b2PolygonShape();
			floorShape.SetAsBox(640/worldScale,10/worldScale);
			// fixture
			var floorFixture:b2FixtureDef = new b2FixtureDef();
			floorFixture.density=0;
			floorFixture.friction=3;
			floorFixture.restitution=0;
			floorFixture.shape=floorShape;
			// body definition
			var floorBodyDef:b2BodyDef = new b2BodyDef();
			floorBodyDef.position.Set(320/worldScale,480/worldScale);
			// the floor itself
			var floor:b2Body=world.CreateBody(floorBodyDef);
			floor.CreateFixture(floorFixture);
			// ************************ THE CAR  ************************ //
			// shape
			var carShape:b2PolygonShape = new b2PolygonShape();
			carShape.SetAsBox(120/worldScale,20/worldScale);
			// fixture
			var carFixture:b2FixtureDef = new b2FixtureDef();
			carFixture.density=5;
			carFixture.friction=3;
			carFixture.restitution=0.3;
			carFixture.filter.groupIndex=-1;
			carFixture.shape=carShape;
			// body definition
			var carBodyDef:b2BodyDef = new b2BodyDef();
			carBodyDef.type=b2Body.b2_dynamicBody;
			carBodyDef.position.Set(320/worldScale,100/worldScale);
			// the car itself
			car=world.CreateBody(carBodyDef);
			car.CreateFixture(carFixture);
			addEventListener(Event.ENTER_FRAME,updateWorld);
		}
		private function debugDraw():void {
			var worldDebugDraw:b2DebugDraw=new b2DebugDraw();
			var debugSprite:Sprite = new Sprite();
			addChild(debugSprite);
			worldDebugDraw.SetSprite(debugSprite);
			worldDebugDraw.SetDrawScale(worldScale);
			worldDebugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
			worldDebugDraw.SetFillAlpha(0.5);
			world.SetDebugDraw(worldDebugDraw);
		}
		private function updateWorld(e:Event):void {
			world.Step(1/30,10,10);
			world.ClearForces();
			world.DrawDebugData();
		}
	}
}

At lines 32-49 a rectangle is added on the world. Here it is:

Since the world is full of cars made by just one rectangle, it’s time to improve our car design.

3 – Adding car parts

Here comes into play Box2D’s capability of creating complex objects by joining simple primitives into compound objects. You can read the theory behind this at the magic of compound objects with Box2D.

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	import Box2D.Dynamics.Joints.*;
	public class Main extends Sprite {
		private var world:b2World=new b2World(new b2Vec2(0,10),true);
		private var worldScale:int=30;
		private var car:b2Body;
		public function Main():void {
			debugDraw();
			// ************************ THE FLOOR ************************ //
			// shape
			var floorShape:b2PolygonShape = new b2PolygonShape();
			floorShape.SetAsBox(640/worldScale,10/worldScale);
			// fixture
			var floorFixture:b2FixtureDef = new b2FixtureDef();
			floorFixture.density=0;
			floorFixture.friction=3;
			floorFixture.restitution=0;
			floorFixture.shape=floorShape;
			// body definition
			var floorBodyDef:b2BodyDef = new b2BodyDef();
			floorBodyDef.position.Set(320/worldScale,480/worldScale);
			// the floor itself
			var floor:b2Body=world.CreateBody(floorBodyDef);
			floor.CreateFixture(floorFixture);
			// ************************ THE CAR ************************ //
			// shape
			var carShape:b2PolygonShape = new b2PolygonShape();
			carShape.SetAsBox(120/worldScale,20/worldScale);
			// fixture
			var carFixture:b2FixtureDef = new b2FixtureDef();
			carFixture.density=5;
			carFixture.friction=3;
			carFixture.restitution=0.3;
			carFixture.filter.groupIndex=-1;
			carFixture.shape=carShape;
			// body definition
			var carBodyDef:b2BodyDef = new b2BodyDef();
			carBodyDef.type=b2Body.b2_dynamicBody;
			carBodyDef.position.Set(320/worldScale,100/worldScale);
			// ************************ THE TRUNK ************************ //
			// shape
			var trunkShape:b2PolygonShape = new b2PolygonShape();
			trunkShape.SetAsOrientedBox(40/worldScale,40/worldScale,new b2Vec2(-80/worldScale,-60/worldScale));
			// fixture
			var trunkFixture:b2FixtureDef = new b2FixtureDef();
			trunkFixture.density=1;
			trunkFixture.friction=3;
			trunkFixture.restitution=0.3;
			trunkFixture.filter.groupIndex=-1;
			trunkFixture.shape=trunkShape;
			// ************************ THE HOOD ************************ //
			// shape
			var hoodShape:b2PolygonShape = new b2PolygonShape();
			var cartVector:Vector.=new Vector.();
			cartVector[0]=new b2Vec2(-40/worldScale,-20/worldScale);
			cartVector[1]=new b2Vec2(-40/worldScale,-100/worldScale);
			cartVector[2]=new b2Vec2(120/worldScale,-20/worldScale);
			hoodShape.SetAsVector(cartVector,3);
			// fixture
			var hoodFixture:b2FixtureDef = new b2FixtureDef();
			hoodFixture.density=1;
			hoodFixture.friction=3;
			hoodFixture.restitution=0.3;
			hoodFixture.filter.groupIndex=-1;
			hoodFixture.shape=hoodShape;
			// ************************ MERGING ALL TOGETHER ************************ //
			// the car itself
			car=world.CreateBody(carBodyDef);
			car.CreateFixture(carFixture);
			car.CreateFixture(trunkFixture);
			car.CreateFixture(hoodFixture);
			addEventListener(Event.ENTER_FRAME,updateWorld);
		}
		private function debugDraw():void {
			var worldDebugDraw:b2DebugDraw=new b2DebugDraw();
			var debugSprite:Sprite = new Sprite();
			addChild(debugSprite);
			worldDebugDraw.SetSprite(debugSprite);
			worldDebugDraw.SetDrawScale(worldScale);
			worldDebugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
			worldDebugDraw.SetFillAlpha(0.5);
			world.SetDebugDraw(worldDebugDraw);
		}
		private function updateWorld(e:Event):void {
			world.Step(1/30,10,10);
			world.ClearForces();
			world.DrawDebugData();
		}
	}
}

We just added a trunk and an hood to the car body. Look how lines 76-78 add car body, trunk and hood to the same b2Body rather than having their own individual b2Body. That’s how you make compound objects.

Also, the hood (a triangle) is created from a vector of points, in the same way slices are made in slicing, splitting and cutting objects with Box2D – part 2.

Now the car itself is ready.

4 – Adding shocks

To simulate shocks, first we need to create axles. Then wheels will be attached to axles, which will be attached to the car body, with some kind of spring effect.

So, at this time, axles are just a pair of boxes (actually they can have almost any shape, I am using boxes for the sake of simplicity).

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	import Box2D.Dynamics.Joints.*;
	public class Main extends Sprite {
		private var world:b2World=new b2World(new b2Vec2(0,10),true);
		private var worldScale:int=30;
		private var car:b2Body;
		public function Main():void {
			debugDraw();
			// ************************ THE FLOOR ************************ //
			// shape
			var floorShape:b2PolygonShape = new b2PolygonShape();
			floorShape.SetAsBox(640/worldScale,10/worldScale);
			// fixture
			var floorFixture:b2FixtureDef = new b2FixtureDef();
			floorFixture.density=0;
			floorFixture.friction=3;
			floorFixture.restitution=0;
			floorFixture.shape=floorShape;
			// body definition
			var floorBodyDef:b2BodyDef = new b2BodyDef();
			floorBodyDef.position.Set(320/worldScale,480/worldScale);
			// the floor itself
			var floor:b2Body=world.CreateBody(floorBodyDef);
			floor.CreateFixture(floorFixture);
			// ************************ THE CAR ************************ //
			// shape
			var carShape:b2PolygonShape = new b2PolygonShape();
			carShape.SetAsBox(120/worldScale,20/worldScale);
			// fixture
			var carFixture:b2FixtureDef = new b2FixtureDef();
			carFixture.density=5;
			carFixture.friction=3;
			carFixture.restitution=0.3;
			carFixture.filter.groupIndex=-1;
			carFixture.shape=carShape;
			// body definition
			var carBodyDef:b2BodyDef = new b2BodyDef();
			carBodyDef.type=b2Body.b2_dynamicBody;
			carBodyDef.position.Set(320/worldScale,100/worldScale);
			// ************************ THE TRUNK ************************ //
			// shape
			var trunkShape:b2PolygonShape = new b2PolygonShape();
			trunkShape.SetAsOrientedBox(40/worldScale,40/worldScale,new b2Vec2(-80/worldScale,-60/worldScale));
			// fixture
			var trunkFixture:b2FixtureDef = new b2FixtureDef();
			trunkFixture.density=1;
			trunkFixture.friction=3;
			trunkFixture.restitution=0.3;
			trunkFixture.filter.groupIndex=-1;
			trunkFixture.shape=trunkShape;
			// ************************ THE HOOD ************************ //
			// shape
			var hoodShape:b2PolygonShape = new b2PolygonShape();
			var carVector:Vector.=new Vector.();
			carVector[0]=new b2Vec2(-40/worldScale,-20/worldScale);
			carVector[1]=new b2Vec2(-40/worldScale,-100/worldScale);
			carVector[2]=new b2Vec2(120/worldScale,-20/worldScale);
			hoodShape.SetAsVector(carVector,3);
			// fixture
			var hoodFixture:b2FixtureDef = new b2FixtureDef();
			hoodFixture.density=1;
			hoodFixture.friction=3;
			hoodFixture.restitution=0.3;
			hoodFixture.filter.groupIndex=-1;
			hoodFixture.shape=hoodShape;
			// ************************ MERGING ALL TOGETHER ************************ //
			// the car itself
			car=world.CreateBody(carBodyDef);
			car.CreateFixture(carFixture);
			car.CreateFixture(trunkFixture);
			car.CreateFixture(hoodFixture);
			// ************************ THE AXLES ************************ //
			// shape
			var axleShape:b2PolygonShape = new b2PolygonShape();
			axleShape.SetAsBox(20/worldScale,20/worldScale);
			// fixture
			var axleFixture:b2FixtureDef = new b2FixtureDef();
			axleFixture.density=0.5;
			axleFixture.friction=3;
			axleFixture.restitution=0.3;
			axleFixture.shape=axleShape;
			axleFixture.filter.groupIndex=-1;
			// body definition
			var axleBodyDef:b2BodyDef = new b2BodyDef();
			axleBodyDef.type=b2Body.b2_dynamicBody;
			// the rear axle itself
			axleBodyDef.position.Set(car.GetWorldCenter().x-(60/worldScale),car.GetWorldCenter().y+(65/worldScale));
			var rearAxle:b2Body=world.CreateBody(axleBodyDef);
			rearAxle.CreateFixture(axleFixture);
			// the front axle itself
			axleBodyDef.position.Set(car.GetWorldCenter().x+(75/worldScale),car.GetWorldCenter().y+(65/worldScale));
			var frontAxle:b2Body=world.CreateBody(axleBodyDef);
			frontAxle.CreateFixture(axleFixture);
			addEventListener(Event.ENTER_FRAME,updateWorld);
		}
		private function debugDraw():void {
			var worldDebugDraw:b2DebugDraw=new b2DebugDraw();
			var debugSprite:Sprite = new Sprite();
			addChild(debugSprite);
			worldDebugDraw.SetSprite(debugSprite);
			worldDebugDraw.SetDrawScale(worldScale);
			worldDebugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
			worldDebugDraw.SetFillAlpha(0.5);
			world.SetDebugDraw(worldDebugDraw);
		}
		private function updateWorld(e:Event):void {
			world.Step(1/30,10,10);
			world.ClearForces();
			world.DrawDebugData();
		}
	}
}

Although I just added a couple of boxes, I would like you to see the magic of this step:

Axles and car body do not collide among themselves because they all have the same groupIndex (lines 41, 56, 71 and 89).

You can find the theory behind groupIndex at Introducing Box2D filtering.

5 – Adding wheels

In the same way I added axles, I am adding wheels. All in all, they are just two circles with the same groupIndex as the rest of the car parts.

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	import Box2D.Dynamics.Joints.*;
	public class Main extends Sprite {
		private var world:b2World=new b2World(new b2Vec2(0,10),true);
		private var worldScale:int=30;
		private var car:b2Body;
		public function Main():void {
			debugDraw();
			// ************************ THE FLOOR ************************ //
			// shape
			var floorShape:b2PolygonShape = new b2PolygonShape();
			floorShape.SetAsBox(640/worldScale,10/worldScale);
			// fixture
			var floorFixture:b2FixtureDef = new b2FixtureDef();
			floorFixture.density=0;
			floorFixture.friction=3;
			floorFixture.restitution=0;
			floorFixture.shape=floorShape;
			// body definition
			var floorBodyDef:b2BodyDef = new b2BodyDef();
			floorBodyDef.position.Set(320/worldScale,480/worldScale);
			// the floor itself
			var floor:b2Body=world.CreateBody(floorBodyDef);
			floor.CreateFixture(floorFixture);
			// ************************ THE CAR ************************ //
			// shape
			var carShape:b2PolygonShape = new b2PolygonShape();
			carShape.SetAsBox(120/worldScale,20/worldScale);
			// fixture
			var carFixture:b2FixtureDef = new b2FixtureDef();
			carFixture.density=5;
			carFixture.friction=3;
			carFixture.restitution=0.3;
			carFixture.filter.groupIndex=-1;
			carFixture.shape=carShape;
			// body definition
			var carBodyDef:b2BodyDef = new b2BodyDef();
			carBodyDef.type=b2Body.b2_dynamicBody;
			carBodyDef.position.Set(320/worldScale,100/worldScale);
			// ************************ THE TRUNK ************************ //
			// shape
			var trunkShape:b2PolygonShape = new b2PolygonShape();
			trunkShape.SetAsOrientedBox(40/worldScale,40/worldScale,new b2Vec2(-80/worldScale,-60/worldScale));
			// fixture
			var trunkFixture:b2FixtureDef = new b2FixtureDef();
			trunkFixture.density=1;
			trunkFixture.friction=3;
			trunkFixture.restitution=0.3;
			trunkFixture.filter.groupIndex=-1;
			trunkFixture.shape=trunkShape;
			// ************************ THE HOOD ************************ //
			// shape
			var hoodShape:b2PolygonShape = new b2PolygonShape();
			var carVector:Vector.=new Vector.();
			carVector[0]=new b2Vec2(-40/worldScale,-20/worldScale);
			carVector[1]=new b2Vec2(-40/worldScale,-100/worldScale);
			carVector[2]=new b2Vec2(120/worldScale,-20/worldScale);
			hoodShape.SetAsVector(carVector,3);
			// fixture
			var hoodFixture:b2FixtureDef = new b2FixtureDef();
			hoodFixture.density=1;
			hoodFixture.friction=3;
			hoodFixture.restitution=0.3;
			hoodFixture.filter.groupIndex=-1;
			hoodFixture.shape=hoodShape;
			// ************************ MERGING ALL TOGETHER ************************ //
			// the car itself
			car=world.CreateBody(carBodyDef);
			car.CreateFixture(carFixture);
			car.CreateFixture(trunkFixture);
			car.CreateFixture(hoodFixture);
			// ************************ THE AXLES ************************ //
			// shape
			var axleShape:b2PolygonShape = new b2PolygonShape();
			axleShape.SetAsBox(20/worldScale,20/worldScale);
			// fixture
			var axleFixture:b2FixtureDef = new b2FixtureDef();
			axleFixture.density=0.5;
			axleFixture.friction=3;
			axleFixture.restitution=0.3;
			axleFixture.shape=axleShape;
			axleFixture.filter.groupIndex=-1;
			// body definition
			var axleBodyDef:b2BodyDef = new b2BodyDef();
			axleBodyDef.type=b2Body.b2_dynamicBody;
			// the rear axle itself
			axleBodyDef.position.Set(car.GetWorldCenter().x-(60/worldScale),car.GetWorldCenter().y+(65/worldScale));
			var rearAxle:b2Body=world.CreateBody(axleBodyDef);
			rearAxle.CreateFixture(axleFixture);
			// the front axle itself
			axleBodyDef.position.Set(car.GetWorldCenter().x+(75/worldScale),car.GetWorldCenter().y+(65/worldScale));
			var frontAxle:b2Body=world.CreateBody(axleBodyDef);
			frontAxle.CreateFixture(axleFixture);
			// ************************ THE WHEELS ************************ //
			// shape
			var wheelShape:b2CircleShape=new b2CircleShape(40/worldScale);
			// fixture
			var wheelFixture:b2FixtureDef = new b2FixtureDef();
			wheelFixture.density=1;
			wheelFixture.friction=3;
			wheelFixture.restitution=0.1;
			wheelFixture.filter.groupIndex=-1;
			wheelFixture.shape=wheelShape;
			// body definition
			var wheelBodyDef:b2BodyDef = new b2BodyDef();
			wheelBodyDef.type=b2Body.b2_dynamicBody;
			// the real wheel itself
			wheelBodyDef.position.Set(car.GetWorldCenter().x-(60/worldScale),car.GetWorldCenter().y+(65/worldScale));
			var rearWheel:b2Body=world.CreateBody(wheelBodyDef);
			rearWheel.CreateFixture(wheelFixture);
			// the front wheel itself
			wheelBodyDef.position.Set(car.GetWorldCenter().x+(75/worldScale),car.GetWorldCenter().y+(65/worldScale));
			var frontWheel:b2Body=world.CreateBody(wheelBodyDef);
			frontWheel.CreateFixture(wheelFixture);
			addEventListener(Event.ENTER_FRAME,updateWorld);
		}
		private function debugDraw():void {
			var worldDebugDraw:b2DebugDraw=new b2DebugDraw();
			var debugSprite:Sprite = new Sprite();
			addChild(debugSprite);
			worldDebugDraw.SetSprite(debugSprite);
			worldDebugDraw.SetDrawScale(worldScale);
			worldDebugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
			worldDebugDraw.SetFillAlpha(0.5);
			world.SetDebugDraw(worldDebugDraw);
		}
		private function updateWorld(e:Event):void {
			world.Step(1/30,10,10);
			world.ClearForces();
			world.DrawDebugData();
		}
	}
}

And here is our car:

Now, it’s time to use joints to connect single car parts and motors to bring them to life:

6 – Attaching wheels to axles

Most of the new code in this script is used to manage keyboard interaction. Now with LEFT and RIGHT arrow keys you can make wheels “run”.

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	import Box2D.Dynamics.Joints.*;
	public class Main extends Sprite {
		private var world:b2World=new b2World(new b2Vec2(0,10),true);
		private var worldScale:int=30;
		private var car:b2Body;
		private var rearWheelRevoluteJoint:b2RevoluteJoint;
		private var frontWheelRevoluteJoint:b2RevoluteJoint;
		private var left:Boolean=false;
		private var right:Boolean=false;
		private var motorSpeed:Number=0;
		public function Main():void {
			debugDraw();
			// ************************ THE FLOOR ************************ //
			// shape
			var floorShape:b2PolygonShape = new b2PolygonShape();
			floorShape.SetAsBox(640/worldScale,10/worldScale);
			// fixture
			var floorFixture:b2FixtureDef = new b2FixtureDef();
			floorFixture.density=0;
			floorFixture.friction=3;
			floorFixture.restitution=0;
			floorFixture.shape=floorShape;
			// body definition
			var floorBodyDef:b2BodyDef = new b2BodyDef();
			floorBodyDef.position.Set(320/worldScale,480/worldScale);
			// the floor itself
			var floor:b2Body=world.CreateBody(floorBodyDef);
			floor.CreateFixture(floorFixture);
			// ************************ THE CAR ************************ //
			// shape
			var carShape:b2PolygonShape = new b2PolygonShape();
			carShape.SetAsBox(120/worldScale,20/worldScale);
			// fixture
			var carFixture:b2FixtureDef = new b2FixtureDef();
			carFixture.density=5;
			carFixture.friction=3;
			carFixture.restitution=0.3;
			carFixture.filter.groupIndex=-1;
			carFixture.shape=carShape;
			// body definition
			var carBodyDef:b2BodyDef = new b2BodyDef();
			carBodyDef.type=b2Body.b2_dynamicBody;
			carBodyDef.position.Set(320/worldScale,100/worldScale);
			// ************************ THE TRUNK ************************ //
			// shape
			var trunkShape:b2PolygonShape = new b2PolygonShape();
			trunkShape.SetAsOrientedBox(40/worldScale,40/worldScale,new b2Vec2(-80/worldScale,-60/worldScale));
			// fixture
			var trunkFixture:b2FixtureDef = new b2FixtureDef();
			trunkFixture.density=1;
			trunkFixture.friction=3;
			trunkFixture.restitution=0.3;
			trunkFixture.filter.groupIndex=-1;
			trunkFixture.shape=trunkShape;
			// ************************ THE HOOD ************************ //
			// shape
			var hoodShape:b2PolygonShape = new b2PolygonShape();
			var carVector:Vector.=new Vector.();
			carVector[0]=new b2Vec2(-40/worldScale,-20/worldScale);
			carVector[1]=new b2Vec2(-40/worldScale,-100/worldScale);
			carVector[2]=new b2Vec2(120/worldScale,-20/worldScale);
			hoodShape.SetAsVector(carVector,3);
			// fixture
			var hoodFixture:b2FixtureDef = new b2FixtureDef();
			hoodFixture.density=1;
			hoodFixture.friction=3;
			hoodFixture.restitution=0.3;
			hoodFixture.filter.groupIndex=-1;
			hoodFixture.shape=hoodShape;
			// ************************ MERGING ALL TOGETHER ************************ //
			// the car itself
			car=world.CreateBody(carBodyDef);
			car.CreateFixture(carFixture);
			car.CreateFixture(trunkFixture);
			car.CreateFixture(hoodFixture);
			// ************************ THE AXLES ************************ //
			// shape
			var axleShape:b2PolygonShape = new b2PolygonShape();
			axleShape.SetAsBox(20/worldScale,20/worldScale);
			// fixture
			var axleFixture:b2FixtureDef = new b2FixtureDef();
			axleFixture.density=0.5;
			axleFixture.friction=3;
			axleFixture.restitution=0.3;
			axleFixture.shape=axleShape;
			axleFixture.filter.groupIndex=-1;
			// body definition
			var axleBodyDef:b2BodyDef = new b2BodyDef();
			axleBodyDef.type=b2Body.b2_dynamicBody;
			// the rear axle itself
			axleBodyDef.position.Set(car.GetWorldCenter().x-(60/worldScale),car.GetWorldCenter().y+(65/worldScale));
			var rearAxle:b2Body=world.CreateBody(axleBodyDef);
			rearAxle.CreateFixture(axleFixture);
			// the front axle itself
			axleBodyDef.position.Set(car.GetWorldCenter().x+(75/worldScale),car.GetWorldCenter().y+(65/worldScale));
			var frontAxle:b2Body=world.CreateBody(axleBodyDef);
			frontAxle.CreateFixture(axleFixture);
			// ************************ THE WHEELS ************************ //
			// shape
			var wheelShape:b2CircleShape=new b2CircleShape(40/worldScale);
			// fixture
			var wheelFixture:b2FixtureDef = new b2FixtureDef();
			wheelFixture.density=1;
			wheelFixture.friction=3;
			wheelFixture.restitution=0.1;
			wheelFixture.filter.groupIndex=-1;
			wheelFixture.shape=wheelShape;
			// body definition
			var wheelBodyDef:b2BodyDef = new b2BodyDef();
			wheelBodyDef.type=b2Body.b2_dynamicBody;
			// the real wheel itself
			wheelBodyDef.position.Set(car.GetWorldCenter().x-(60/worldScale),car.GetWorldCenter().y+(65/worldScale));
			var rearWheel:b2Body=world.CreateBody(wheelBodyDef);
			rearWheel.CreateFixture(wheelFixture);
			// the front wheel itself
			wheelBodyDef.position.Set(car.GetWorldCenter().x+(75/worldScale),car.GetWorldCenter().y+(65/worldScale));
			var frontWheel:b2Body=world.CreateBody(wheelBodyDef);
			frontWheel.CreateFixture(wheelFixture);
			// ************************ REVOLUTE JOINTS ************************ //
			// rear joint
			var rearWheelRevoluteJointDef:b2RevoluteJointDef=new b2RevoluteJointDef();
			rearWheelRevoluteJointDef.Initialize(rearWheel,rearAxle,rearWheel.GetWorldCenter());
			rearWheelRevoluteJointDef.enableMotor=true;
			rearWheelRevoluteJointDef.maxMotorTorque=10000;
			rearWheelRevoluteJoint=world.CreateJoint(rearWheelRevoluteJointDef) as b2RevoluteJoint;
			// front joint
			var frontWheelRevoluteJointDef:b2RevoluteJointDef=new b2RevoluteJointDef();
			frontWheelRevoluteJointDef.Initialize(frontWheel,frontAxle,frontWheel.GetWorldCenter());
			frontWheelRevoluteJointDef.enableMotor=true;
			frontWheelRevoluteJointDef.maxMotorTorque=10000;
			frontWheelRevoluteJoint=world.CreateJoint(frontWheelRevoluteJointDef) as b2RevoluteJoint;
			addEventListener(Event.ENTER_FRAME,updateWorld);
			stage.addEventListener(KeyboardEvent.KEY_DOWN,keyPressed);
			stage.addEventListener(KeyboardEvent.KEY_UP,keyReleased);
		}
		private function debugDraw():void {
			var worldDebugDraw:b2DebugDraw=new b2DebugDraw();
			var debugSprite:Sprite = new Sprite();
			addChild(debugSprite);
			worldDebugDraw.SetSprite(debugSprite);
			worldDebugDraw.SetDrawScale(worldScale);
			worldDebugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
			worldDebugDraw.SetFillAlpha(0.5);
			world.SetDebugDraw(worldDebugDraw);
		}
		private function keyPressed(e:KeyboardEvent):void {
			switch (e.keyCode) {
				case 37 :
					left=true;
					break;
				case 39 :
					right=true;
					break;
			}
		}
		private function keyReleased(e:KeyboardEvent):void {
			switch (e.keyCode) {
				case 37 :
					left=false;
					break;
				case 39 :
					right=false;
					break;
			}
		}
		private function updateWorld(e:Event):void {
			if (left) {
				motorSpeed+=0.5;
			}
			if (right) {
				motorSpeed-=0.5;
			}
			motorSpeed*=0.99;
			if (motorSpeed>100) {
				motorSpeed=100;
			}
			rearWheelRevoluteJoint.SetMotorSpeed(motorSpeed);
			frontWheelRevoluteJoint.SetMotorSpeed(motorSpeed);
			world.Step(1/30,10,10);
			world.ClearForces();
			world.DrawDebugData();
		}
	}
}

Anyway, the interesting thing lies in the use of revolute joints (theory: Box2D joints: Revolute Joint) to attach the wheels to axles and motors (theory: Box2D joints: Revolute Joint – Building motors) to make wheels move.

maxMotorTorque property defines the maximum motor torque used to achieve the desired motor speed, while SetMotorSpeed method sets the motor speed in radians per second.

Press LEFT and RIGHT arrow keys to make wheels roll. Now, the last step.

7 – Attaching axles to car body

We will use prismatic joints to attach axles to the car. You can find the theory at Box2D joints: Prismatic Joints.

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	import Box2D.Dynamics.Joints.*;
	public class Main extends Sprite {
		private var world:b2World=new b2World(new b2Vec2(0,10),true);
		private var worldScale:int=30;
		private var car:b2Body;
		private var rearWheelRevoluteJoint:b2RevoluteJoint;
		private var frontWheelRevoluteJoint:b2RevoluteJoint;
		private var left:Boolean=false;
		private var right:Boolean=false;
		private var motorSpeed:Number=0;
		private var frontAxlePrismaticJoint:b2PrismaticJoint;
		private var rearAxlePrismaticJoint:b2PrismaticJoint;
		public function Main():void {
			debugDraw();
			// ************************ THE FLOOR ************************ //
			// shape
			var floorShape:b2PolygonShape = new b2PolygonShape();
			floorShape.SetAsBox(640/worldScale,10/worldScale);
			// fixture
			var floorFixture:b2FixtureDef = new b2FixtureDef();
			floorFixture.density=0;
			floorFixture.friction=3;
			floorFixture.restitution=0;
			floorFixture.shape=floorShape;
			// body definition
			var floorBodyDef:b2BodyDef = new b2BodyDef();
			floorBodyDef.position.Set(320/worldScale,480/worldScale);
			// the floor itself
			var floor:b2Body=world.CreateBody(floorBodyDef);
			floor.CreateFixture(floorFixture);
			// ************************ THE CAR ************************ //
			// shape
			var carShape:b2PolygonShape = new b2PolygonShape();
			carShape.SetAsBox(120/worldScale,20/worldScale);
			// fixture
			var carFixture:b2FixtureDef = new b2FixtureDef();
			carFixture.density=5;
			carFixture.friction=3;
			carFixture.restitution=0.3;
			carFixture.filter.groupIndex=-1;
			carFixture.shape=carShape;
			// body definition
			var carBodyDef:b2BodyDef = new b2BodyDef();
			carBodyDef.type=b2Body.b2_dynamicBody;
			carBodyDef.position.Set(320/worldScale,100/worldScale);
			// ************************ THE TRUNK ************************ //
			// shape
			var trunkShape:b2PolygonShape = new b2PolygonShape();
			trunkShape.SetAsOrientedBox(40/worldScale,40/worldScale,new b2Vec2(-80/worldScale,-60/worldScale));
			// fixture
			var trunkFixture:b2FixtureDef = new b2FixtureDef();
			trunkFixture.density=1;
			trunkFixture.friction=3;
			trunkFixture.restitution=0.3;
			trunkFixture.filter.groupIndex=-1;
			trunkFixture.shape=trunkShape;
			// ************************ THE HOOD ************************ //
			// shape
			var hoodShape:b2PolygonShape = new b2PolygonShape();
			var carVector:Vector.=new Vector.();
			carVector[0]=new b2Vec2(-40/worldScale,-20/worldScale);
			carVector[1]=new b2Vec2(-40/worldScale,-100/worldScale);
			carVector[2]=new b2Vec2(120/worldScale,-20/worldScale);
			hoodShape.SetAsVector(carVector,3);
			// fixture
			var hoodFixture:b2FixtureDef = new b2FixtureDef();
			hoodFixture.density=1;
			hoodFixture.friction=3;
			hoodFixture.restitution=0.3;
			hoodFixture.filter.groupIndex=-1;
			hoodFixture.shape=hoodShape;
			// ************************ MERGING ALL TOGETHER ************************ //
			// the car itself
			car=world.CreateBody(carBodyDef);
			car.CreateFixture(carFixture);
			car.CreateFixture(trunkFixture);
			car.CreateFixture(hoodFixture);
			// ************************ THE AXLES ************************ //
			// shape
			var axleShape:b2PolygonShape = new b2PolygonShape();
			axleShape.SetAsBox(20/worldScale,20/worldScale);
			// fixture
			var axleFixture:b2FixtureDef = new b2FixtureDef();
			axleFixture.density=0.5;
			axleFixture.friction=3;
			axleFixture.restitution=0.3;
			axleFixture.shape=axleShape;
			axleFixture.filter.groupIndex=-1;
			// body definition
			var axleBodyDef:b2BodyDef = new b2BodyDef();
			axleBodyDef.type=b2Body.b2_dynamicBody;
			// the rear axle itself
			axleBodyDef.position.Set(car.GetWorldCenter().x-(60/worldScale),car.GetWorldCenter().y+(65/worldScale));
			var rearAxle:b2Body=world.CreateBody(axleBodyDef);
			rearAxle.CreateFixture(axleFixture);
			// the front axle itself
			axleBodyDef.position.Set(car.GetWorldCenter().x+(75/worldScale),car.GetWorldCenter().y+(65/worldScale));
			var frontAxle:b2Body=world.CreateBody(axleBodyDef);
			frontAxle.CreateFixture(axleFixture);
			// ************************ THE WHEELS ************************ //
			// shape
			var wheelShape:b2CircleShape=new b2CircleShape(40/worldScale);
			// fixture
			var wheelFixture:b2FixtureDef = new b2FixtureDef();
			wheelFixture.density=1;
			wheelFixture.friction=3;
			wheelFixture.restitution=0.1;
			wheelFixture.filter.groupIndex=-1;
			wheelFixture.shape=wheelShape;
			// body definition
			var wheelBodyDef:b2BodyDef = new b2BodyDef();
			wheelBodyDef.type=b2Body.b2_dynamicBody;
			// the real wheel itself
			wheelBodyDef.position.Set(car.GetWorldCenter().x-(60/worldScale),car.GetWorldCenter().y+(65/worldScale));
			var rearWheel:b2Body=world.CreateBody(wheelBodyDef);
			rearWheel.CreateFixture(wheelFixture);
			// the front wheel itself
			wheelBodyDef.position.Set(car.GetWorldCenter().x+(75/worldScale),car.GetWorldCenter().y+(65/worldScale));
			var frontWheel:b2Body=world.CreateBody(wheelBodyDef);
			frontWheel.CreateFixture(wheelFixture);
			// ************************ REVOLUTE JOINTS ************************ //
			// rear joint
			var rearWheelRevoluteJointDef:b2RevoluteJointDef=new b2RevoluteJointDef();
			rearWheelRevoluteJointDef.Initialize(rearWheel,rearAxle,rearWheel.GetWorldCenter());
			rearWheelRevoluteJointDef.enableMotor=true;
			rearWheelRevoluteJointDef.maxMotorTorque=10000;
			rearWheelRevoluteJoint=world.CreateJoint(rearWheelRevoluteJointDef) as b2RevoluteJoint;
			// front joint
			var frontWheelRevoluteJointDef:b2RevoluteJointDef=new b2RevoluteJointDef();
			frontWheelRevoluteJointDef.Initialize(frontWheel,frontAxle,frontWheel.GetWorldCenter());
			frontWheelRevoluteJointDef.enableMotor=true;
			frontWheelRevoluteJointDef.maxMotorTorque=10000;
			frontWheelRevoluteJoint=world.CreateJoint(frontWheelRevoluteJointDef) as b2RevoluteJoint;
			// ************************ PRISMATIC JOINTS ************************ //
			//  definition
			var axlePrismaticJointDef:b2PrismaticJointDef=new b2PrismaticJointDef();
			axlePrismaticJointDef.lowerTranslation=-20/worldScale;
			axlePrismaticJointDef.upperTranslation=5/worldScale;
			axlePrismaticJointDef.enableLimit=true;
			axlePrismaticJointDef.enableMotor=true;
			// front axle
			axlePrismaticJointDef.Initialize(car,frontAxle,frontAxle.GetWorldCenter(),new b2Vec2(0,1));
			frontAxlePrismaticJoint=world.CreateJoint(axlePrismaticJointDef) as b2PrismaticJoint;
			// rear axle
			axlePrismaticJointDef.Initialize(car,rearAxle,rearAxle.GetWorldCenter(),new b2Vec2(0,1));
			rearAxlePrismaticJoint=world.CreateJoint(axlePrismaticJointDef) as b2PrismaticJoint;
			addEventListener(Event.ENTER_FRAME,updateWorld);
			stage.addEventListener(KeyboardEvent.KEY_DOWN,keyPressed);
			stage.addEventListener(KeyboardEvent.KEY_UP,keyReleased);
		}
		private function debugDraw():void {
			var worldDebugDraw:b2DebugDraw=new b2DebugDraw();
			var debugSprite:Sprite = new Sprite();
			addChild(debugSprite);
			worldDebugDraw.SetSprite(debugSprite);
			worldDebugDraw.SetDrawScale(worldScale);
			worldDebugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
			worldDebugDraw.SetFillAlpha(0.5);
			world.SetDebugDraw(worldDebugDraw);
		}
		private function keyPressed(e:KeyboardEvent):void {
			switch (e.keyCode) {
				case 37 :
					left=true;
					break;
				case 39 :
					right=true;
					break;
			}
		}
		private function keyReleased(e:KeyboardEvent):void {
			switch (e.keyCode) {
				case 37 :
					left=false;
					break;
				case 39 :
					right=false;
					break;
			}
		}
		private function updateWorld(e:Event):void {
			if (left) {
				motorSpeed+=0.5;
			}
			if (right) {
				motorSpeed-=0.5;
			}
			motorSpeed*=0.99;
			if (motorSpeed>100) {
				motorSpeed=100;
			}
			rearWheelRevoluteJoint.SetMotorSpeed(motorSpeed);
			frontWheelRevoluteJoint.SetMotorSpeed(motorSpeed);
			frontAxlePrismaticJoint.SetMaxMotorForce(Math.abs(600*frontAxlePrismaticJoint.GetJointTranslation()));
			frontAxlePrismaticJoint.SetMotorSpeed((frontAxlePrismaticJoint.GetMotorSpeed()-2*frontAxlePrismaticJoint.GetJointTranslation()));
			rearAxlePrismaticJoint.SetMaxMotorForce(Math.abs(600*rearAxlePrismaticJoint.GetJointTranslation()));
			rearAxlePrismaticJoint.SetMotorSpeed((rearAxlePrismaticJoint.GetMotorSpeed()-2*rearAxlePrismaticJoint.GetJointTranslation()));
			world.Step(1/30,10,10);
			world.ClearForces();
			world.DrawDebugData();
		}
	}
}

Prismatic joints can act like springs if you play with their motor properties. You will find how to create your custom car after this final result:

Here shocks bounce a lot because I wanted to show your how does everything work.

Customize your car

Here is a list of parameters you can change to create your custom car:

Car mass: modifying the density of the car body, the trunk and the hood (lines 45, 60 and 75) you will change the car mass, its weight and eventually its center of gravity if you make (example) an heavy trunk and a light hood.

Axles position: can be changed at lines 101 and 105. Normally a long distance between front and rear axle makes the car more stable.

Wheels radius: can be changed at line 110.

Wheels motor torque: you can change it at lines 134 and 140. The higher the torque, the lower the time to bring wheels to desired speed.

Wheels speed: at lines 200-201 you can set the speed of the wheels.

Shocks orientation: at lines 150 and 153 you can change shocks orientation. It’s the last argument.

Shocks excursion: changing lines 145-146 will affect prismatic joints upper and lower limits, modifying shocks excursion.

Shocks feedback: shocks motor speed and max force (just like wheels speed and max torque) are updated at lines 202-205 in real time at every frame to simulate the spring effect.

Download the source code.

Can you find a good mix of these parameters to create a well responsive car?

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

215 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stairs
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers