Get the full commented source code of

HTML5 Suika Watermelon Game

Talking about Actionscript 3, Box2D and Flash.

One of the apparently hardest things to do with Box2D is assigning different gravity forces to different objects.

For instance, you may want the world to be ruled with regular gravity, while you don’t want the bullet fired by your character to be affected, or you may want regular gravity for all boxes and inverted one for all circles, like in the example I am talking about.

There is one way to simulate different gravity and one way to apply different gravity in your Box2D projects.

This is the main script, directly from the HelloWorld.as example provided with Box2D distribution (so just copy/paste the code)

In some case you should need to refresh the page to see the movies in action.

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	public class HelloWorld extends Sprite {
		public var m_world:b2World;
		public var m_iterations:int=10;
		public var m_timeStep:Number=1.0/30.0;
		public function HelloWorld() {
			addEventListener(Event.ENTER_FRAME, Update, false, 0, true);
			var worldAABB:b2AABB = new b2AABB();
			worldAABB.lowerBound.Set(-100.0, -100.0);
			worldAABB.upperBound.Set(100.0, 100.0);
			var gravity:b2Vec2=new b2Vec2(0.0,10.0);
			var doSleep:Boolean=true;
			m_world=new b2World(worldAABB,gravity,doSleep);
			var dbgDraw:b2DebugDraw = new b2DebugDraw();
			var dbgSprite:Sprite = new Sprite();
			addChild(dbgSprite);
			dbgDraw.m_sprite=dbgSprite;
			dbgDraw.m_drawScale=30.0;
			dbgDraw.m_fillAlpha=0.5;
			dbgDraw.m_lineThickness=1.0;
			dbgDraw.m_drawFlags=b2DebugDraw.e_shapeBit;
			m_world.SetDebugDraw(dbgDraw);
			var body:b2Body;
			var bodyDef:b2BodyDef;
			var boxDef:b2PolygonDef;
			var circleDef:b2CircleDef;
			bodyDef = new b2BodyDef();
			bodyDef.position.Set(10, 12);
			boxDef = new b2PolygonDef();
			boxDef.SetAsBox(30, 0.5);
			boxDef.friction=0.3;
			boxDef.density=0;
			body=m_world.CreateBody(bodyDef);
			body.CreateShape(boxDef);
			body.SetMassFromShapes();
			bodyDef = new b2BodyDef();
			bodyDef.position.Set(10, 0);
			boxDef = new b2PolygonDef();
			boxDef.SetAsBox(30, 0.5);
			boxDef.friction=0.3;
			boxDef.density=0;
			body=m_world.CreateBody(bodyDef);
			body.CreateShape(boxDef);
			body.SetMassFromShapes();
			for (var i:int = 1; i < 10; i++) {
				bodyDef = new b2BodyDef();
				bodyDef.position.x=Math.random()*12+2;
				bodyDef.position.y=Math.random()+5;
				var rX:Number=Math.random()+0.2;
				var rY:Number=Math.random()+0.2;
				if (Math.random()<0.5) {
					boxDef = new b2PolygonDef();
					boxDef.SetAsBox(rX, rY);
					boxDef.density=1.0;
					boxDef.friction=0.5;
					boxDef.restitution=0.2;
					bodyDef.userData = new Sprite();
					bodyDef.userData.name="box";
					body=m_world.CreateBody(bodyDef);
					body.CreateShape(boxDef);
				} else {
					circleDef = new b2CircleDef();
					circleDef.radius=rX;
					circleDef.density=1.0;
					circleDef.friction=0.5;
					circleDef.restitution=0.2;
					bodyDef.userData = new Sprite();
					bodyDef.userData.name="circle";
					body=m_world.CreateBody(bodyDef);
					body.CreateShape(circleDef);
				}
				body.SetMassFromShapes();
				addChild(bodyDef.userData);
			}
		}
		public function Update(e:Event):void {
			m_world.Step(m_timeStep, m_iterations);
		}
	}
}

The only interesting lines are line 17 where I declare the gravity vector and lines 64 and 74 where I assign names to the objects... box for boxes and circle for circles

And this is the result:

As you can see, the world is ruled by standard gravity, but as I said I want boxes to be ruled by normal gravity and circles to be ruled by inverted gravity

Now, let's see the first method, called (by myself)....

The antagonist forces method

As the name means, the principle is applying an antagonist force to circles in order to simulate a reverse gravity. Since the default gravity is (0,10), I am going to apply a (0,-20) force to all circles.

This is the updated Update function:

public function Update(e:Event):void {
	var ant_gravity = b2Vec2;
	m_world.Step(m_timeStep, m_iterations);
	for (var bb:b2Body = m_world.m_bodyList; bb; bb = bb.m_next) {
		if (bb.GetUserData()!=null) {
			if (bb.GetUserData().name=="circle") {
				ant_gravity = new b2Vec2(0.0,-20.0*bb.GetMass());
				bb.ApplyForce(ant_gravity,bb.GetWorldCenter());
			}
		}
	}
}

And this is the result:

The result is what we want, anyway now you may say to create objects that aren't affected by gravity you just have to change line 88 with

ant_gravity = new b2Vec2(0.0,-10.0*bb.GetMass());

but look at the result...

circles are slowly falling down. This happens because when you call Step functions, bodies have an acceleration due to gravity, and the opposite force isn't enough to nullify it.

Let's see the second method, called

The island method

This method is named after the filename of the library you have to edit... it's b2Island.as inside Dynamics folder.

First, the only line inside Update function must be once again

m_world.Step(m_timeStep, m_iterations);

as in the first example.

Then, this is the modified version of Solve function inside b2Island.as file:

public function Solve(step:b2TimeStep, gravity:b2Vec2, correctPositions:Boolean, allowSleep:Boolean) : void
{
	var i:int;
	var b:b2Body;
	var joint:b2Joint;
	var applied_gravity:b2Vec2
	
	// Integrate velocities and apply damping.
	for (i = 0; i < m_bodyCount; ++i)
	{
		b = m_bodies[i];
		
		if (b.IsStatic())
			continue;
		
		// Integrate velocities.
		//b.m_linearVelocity += step.dt * (gravity + b.m_invMass * b.m_force);
		if(b.m_userData.name=="circle"){
			applied_gravity = new b2Vec2(-gravity.x,-gravity.y)
		}
		else{
			applied_gravity = gravity;
		}
		b.m_linearVelocity.x += step.dt * (applied_gravity.x + b.m_invMass * b.m_force.x);
		b.m_linearVelocity.y += step.dt * (applied_gravity.y + b.m_invMass * b.m_force.y);
		b.m_angularVelocity += step.dt * b.m_invI * b.m_torque;
		
		// Reset forces.
		b.m_force.SetZero();
		b.m_torque = 0.0;
		
		// Apply damping.
		// ODE: dv/dt + c * v = 0
		// Solution: v(t) = v0 * exp(-c * t)
		// Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt)
		// v2 = exp(-c * dt) * v1
		// Taylor expansion:
		// v2 = (1.0f - c * dt) * v1
		b.m_linearVelocity.Multiply( b2Math.b2Clamp(1.0 - step.dt * b.m_linearDamping, 0.0, 1.0) );
		b.m_angularVelocity *= b2Math.b2Clamp(1.0 - step.dt * b.m_angularDamping, 0.0, 1.0);
		
		// Check for large velocities.
		//if (b2Dot(b->m_linearVelocity, b->m_linearVelocity) > b2_maxLinearVelocitySquared)
		if ((b.m_linearVelocity.LengthSquared()) > b2Settings.b2_maxLinearVelocitySquared)
		{
			b.m_linearVelocity.Normalize();
			b.m_linearVelocity.x *= b2Settings.b2_maxLinearVelocity;
			b.m_linearVelocity.y *= b2Settings.b2_maxLinearVelocity;
		}
		
		if (b.m_angularVelocity * b.m_angularVelocity > b2Settings.b2_maxAngularVelocitySquared)
		{
			if (b.m_angularVelocity < 0.0)
			{
				b.m_angularVelocity = -b2Settings.b2_maxAngularVelocity;
			}
			else
			{
				b.m_angularVelocity = b2Settings.b2_maxAngularVelocity;
			}
		}
	}
	
	var contactSolver:b2ContactSolver = new b2ContactSolver(step, m_contacts, m_contactCount, m_allocator);
	
	// Initialize velocity constraints.
	contactSolver.InitVelocityConstraints(step);
	
	for (i = 0; i < m_jointCount; ++i)
	{
		joint = m_joints[i];
		joint.InitVelocityConstraints(step);
	}
	
	// Solve velocity constraints.
	for (i = 0; i < step.maxIterations; ++i)
	{
		contactSolver.SolveVelocityConstraints();
		
		for (var j:int = 0; j < m_jointCount; ++j)
		{
			joint = m_joints[j];
			joint.SolveVelocityConstraints(step);
		}
	}
	
	// Post-solve (store impulses for warm starting).
	contactSolver.FinalizeVelocityConstraints();
	
	// Integrate positions.
	for (i = 0; i < m_bodyCount; ++i)
	{
		b = m_bodies[i];
		
		if (b.IsStatic())
			continue;
		
		// Store positions for continuous collision.
		b.m_sweep.c0.SetV(b.m_sweep.c);
		b.m_sweep.a0 = b.m_sweep.a;
		
		// Integrate
		//b.m_sweep.c += step.dt * b.m_linearVelocity;
		b.m_sweep.c.x += step.dt * b.m_linearVelocity.x;
		b.m_sweep.c.y += step.dt * b.m_linearVelocity.y;
		b.m_sweep.a += step.dt * b.m_angularVelocity;
		
		// Compute new transform
		b.SynchronizeTransform();
		
		// Note: shapes are synchronized later.
	}
	
	if (correctPositions)
	{
		// Initialize position constraints.
		// Contacts don't need initialization.
		for (i = 0; i < m_jointCount; ++i)
		{
			joint = m_joints[i];
			joint.InitPositionConstraints();
		}
		
		// Iterate over constraints.
		for (m_positionIterationCount = 0; m_positionIterationCount < step.maxIterations; ++m_positionIterationCount)
		{
			var contactsOkay:Boolean = contactSolver.SolvePositionConstraints(b2Settings.b2_contactBaumgarte);
			
			var jointsOkay:Boolean = true;
			for (i = 0; i < m_jointCount; ++i)
			{
				joint = m_joints[i];
				var jointOkay:Boolean = joint.SolvePositionConstraints();
				jointsOkay = jointsOkay && jointOkay;
			}
			
			if (contactsOkay && jointsOkay)
			{
				break;
			}
		}
	}
	
	Report(contactSolver.m_constraints);
	
	if (allowSleep){
		
		var minSleepTime:Number = Number.MAX_VALUE;
		
		var linTolSqr:Number = b2Settings.b2_linearSleepTolerance * b2Settings.b2_linearSleepTolerance;
		var angTolSqr:Number = b2Settings.b2_angularSleepTolerance * b2Settings.b2_angularSleepTolerance;
		
		for (i = 0; i < m_bodyCount; ++i)
		{
			b = m_bodies[i];
			if (b.m_invMass == 0.0)
			{
				continue;
			}
			
			if ((b.m_flags & b2Body.e_allowSleepFlag) == 0)
			{
				b.m_sleepTime = 0.0;
				minSleepTime = 0.0;
			}
			
			if ((b.m_flags & b2Body.e_allowSleepFlag) == 0 ||
				b.m_angularVelocity * b.m_angularVelocity > angTolSqr ||
				b2Math.b2Dot(b.m_linearVelocity, b.m_linearVelocity) > linTolSqr)
			{
				b.m_sleepTime = 0.0;
				minSleepTime = 0.0;
			}
			else
			{
				b.m_sleepTime += step.dt;
				minSleepTime = b2Math.b2Min(minSleepTime, b.m_sleepTime);
			}
		}
		
		if (minSleepTime >= b2Settings.b2_timeToSleep)
		{
			for (i = 0; i < m_bodyCount; ++i)
			{
				b = m_bodies[i];
				b.m_flags |= b2Body.e_sleepFlag;
				b.m_linearVelocity.SetZero();
				b.m_angularVelocity = 0.0;
			}
		}
	}
}

Let's see the lines I added/changed:

Line 162: declaring a new vector that will handle the applied gravity

Line 174: if the body we are solving is called "circle"...

Line 175: set applied_gravity vector as the opposite of the default gravity one

Lines 177-179: If not, set applied_gravity vector to default gravity one

Lines 180-181: Use applied_gravity vector instead of gravity vector to determine body's linear velocity

And this is the result:

That is working perfectly even if you change line 175 this way:

applied_gravity = new b2Vec2(0,0)

To have no gravity circles, so this last method is preferred.

Let me think what do you think about it.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.