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.