Do you like my tutorials?

Then consider supporting me on Ko-fi

Talking about Actionscript 3, Box2D and Flash.

As you should know, Box2D only manages rigid bodies, this means it assumes its bodies are made of the hardest material of the world, which cannot be bent or deformed in any way.

This saves a lot of CPU when handling the simulation but what if we need a soft body, such as a blob?

Around the web you can find some interesting examples based on springs taken from Boris the Brave’s controller pack but I’ve found them (and the author says the are) a bit glitchy.

So here we go with a step by step creation of a soft body blob only using distance joints.

1) Creating the bodies

The first step consists in creating the collection of bodies which will represent the soft body. There is a main body, and a number of satellite bodies, as you can see from this script:

package {
	import flash.display.Sprite;
	import flash.events.Event;
	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;
		private var worldScale:Number=30;
		private var sphereVector:Vector.;
		private var blobX:Number=320;
		private var blobY:Number=240;
		private var particleNumber:Number=16;
		private var particleDistance:Number=50;
		public function Main() {
			world=new b2World(new b2Vec2(0,10),true);
			debugDraw();
			floor();
			sphereVector=new Vector.();
			sphereVector.push(sphere(blobX,blobY,15));
			for (var i:Number=0; i

Pay particular attention to particleNumber variable which represents the number of satellite bodies which will build the blob itself, and to particleDistance which basically is the radius of the blob.

This is the result:

These are all the bodies we need to build the blob.

2) Binding satellites with the main body

Now I am going to create distance joints to bind satellites with the main body

package {
	import flash.display.Sprite;
	import flash.events.Event;
	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;
		private var worldScale:Number=30;
		private var sphereVector:Vector.;
		private var blobX:Number=320;
		private var blobY:Number=240;
		private var particleNumber:Number=16;
		private var particleDistance:Number=50;
		public function Main() {
			world=new b2World(new b2Vec2(0,10),true);
			debugDraw();
			floor();
			sphereVector=new Vector.();
			sphereVector.push(sphere(blobX,blobY,15));
			for (var i:Number=0; i

At the end of this step, every satellite has its distance joint with the main body:

However, just using these joints is not enough, as I also need to create distance joints between satellites

3) Binding satellites one to each other

Now it's time to add some other joints to bind satellites one to each other, this way, so we can actually get some kind of wheel

package {
	import flash.display.Sprite;
	import flash.events.Event;
	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;
		private var worldScale:Number=30;
		private var sphereVector:Vector.;
		private var blobX:Number=320;
		private var blobY:Number=240;
		private var particleNumber:Number=16;
		private var particleDistance:Number=50;
		public function Main() {
			world=new b2World(new b2Vec2(0,10),true);
			debugDraw();
			floor();
			sphereVector=new Vector.();
			sphereVector.push(sphere(blobX,blobY,15));
			for (var i:Number=0; i0) {
					var distanceX:Number=posX/worldScale-sphereVector[sphereVector.length-2].GetPosition().x;
					var distanceY:Number=posY/worldScale-sphereVector[sphereVector.length-2].GetPosition().y;
					var distance:Number=Math.sqrt(distanceX*distanceX+distanceY*distanceY);
					dJoint.bodyA=sphereVector[sphereVector.length-2];
					dJoint.bodyB=sphereVector[sphereVector.length-1];
					dJoint.localAnchorA=new b2Vec2(0,0);
					dJoint.localAnchorB=new b2Vec2(0,0);
					dJoint.length=distance;
					distanceJoint=world.CreateJoint(dJoint) as b2DistanceJoint;
				}
				if (i==particleNumber-1) {
					distanceX=posX/worldScale-sphereVector[1].GetPosition().x;
					distanceY=posY/worldScale-sphereVector[1].GetPosition().y;
					distance=Math.sqrt(distanceX*distanceX+distanceY*distanceY);
					dJoint.bodyA=sphereVector[1];
					dJoint.bodyB=sphereVector[sphereVector.length-1];
					dJoint.localAnchorA=new b2Vec2(0,0);
					dJoint.localAnchorB=new b2Vec2(0,0);
					dJoint.length=distance;
					distanceJoint=world.CreateJoint(dJoint) as b2DistanceJoint;
				}
			}
			addEventListener(Event.ENTER_FRAME,updateWorld);
		}
		private function sphere(pX:int,pY:int,r:Number):b2Body {
			var bodyDef:b2BodyDef=new b2BodyDef();
			bodyDef.position.Set(pX/worldScale,pY/worldScale);
			var circleShape:b2CircleShape;
			circleShape=new b2CircleShape(r/worldScale);
			var fixtureDef:b2FixtureDef=new b2FixtureDef();
			fixtureDef.shape=circleShape;
			fixtureDef.density=1;
			fixtureDef.restitution=0.4;
			fixtureDef.friction=0.5;
			var theSphere:b2Body=world.CreateBody(bodyDef);
			theSphere.CreateFixture(fixtureDef);
			return theSphere;
		}
		private function floor():void {
			var bodyDef:b2BodyDef=new b2BodyDef();
			bodyDef.position.Set(320/worldScale,465/worldScale);
			var polygonShape:b2PolygonShape=new b2PolygonShape();
			polygonShape.SetAsBox(320/worldScale,15/worldScale);
			var fixtureDef:b2FixtureDef=new b2FixtureDef();
			fixtureDef.shape=polygonShape;
			fixtureDef.restitution=0.4;
			fixtureDef.friction=0.5;
			var theFloor:b2Body=world.CreateBody(bodyDef);
			theFloor.CreateFixture(fixtureDef);
		}
		private function debugDraw():void {
			var debugDraw:b2DebugDraw=new b2DebugDraw();
			var debugSprite:Sprite=new Sprite();
			addChild(debugSprite);
			debugDraw.SetSprite(debugSprite);
			debugDraw.SetDrawScale(worldScale);
			debugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
			debugDraw.SetFillAlpha(0.5);
			world.SetDebugDraw(debugDraw);
		}
		private function updateWorld(e:Event):void {
			world.Step(1/30,10,10);
			world.ClearForces();
			world.DrawDebugData();
		}
	}
}

And now the blob is ready to be seen in action:

Nothing happens because all bodies are still static

4) Making bodies dynamic

It's time to make bodies dynamic and see what happens:

package {
	import flash.display.Sprite;
	import flash.events.Event;
	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;
		private var worldScale:Number=30;
		private var sphereVector:Vector.;
		private var blobX:Number=320;
		private var blobY:Number=240;
		private var particleNumber:Number=16;
		private var particleDistance:Number=50;
		public function Main() {
			world=new b2World(new b2Vec2(0,10),true);
			debugDraw();
			floor();
			sphereVector=new Vector.();
			sphereVector.push(sphere(blobX,blobY,15));
			for (var i:Number=0; i0) {
					var distanceX:Number=posX/worldScale-sphereVector[sphereVector.length-2].GetPosition().x;
					var distanceY:Number=posY/worldScale-sphereVector[sphereVector.length-2].GetPosition().y;
					var distance:Number=Math.sqrt(distanceX*distanceX+distanceY*distanceY);
					dJoint.bodyA=sphereVector[sphereVector.length-2];
					dJoint.bodyB=sphereVector[sphereVector.length-1];
					dJoint.localAnchorA=new b2Vec2(0,0);
					dJoint.localAnchorB=new b2Vec2(0,0);
					dJoint.length=distance;
					distanceJoint=world.CreateJoint(dJoint) as b2DistanceJoint;
				}
				if (i==particleNumber-1) {
					distanceX=posX/worldScale-sphereVector[1].GetPosition().x;
					distanceY=posY/worldScale-sphereVector[1].GetPosition().y;
					distance=Math.sqrt(distanceX*distanceX+distanceY*distanceY);
					dJoint.bodyA=sphereVector[1];
					dJoint.bodyB=sphereVector[sphereVector.length-1];
					dJoint.localAnchorA=new b2Vec2(0,0);
					dJoint.localAnchorB=new b2Vec2(0,0);
					dJoint.length=distance;
					distanceJoint=world.CreateJoint(dJoint) as b2DistanceJoint;
				}
			}
			addEventListener(Event.ENTER_FRAME,updateWorld);
		}
		private function sphere(pX:int,pY:int,r:Number):b2Body {
			var bodyDef:b2BodyDef=new b2BodyDef();
			bodyDef.position.Set(pX/worldScale,pY/worldScale);
			bodyDef.type=b2Body.b2_dynamicBody;
			var circleShape:b2CircleShape;
			circleShape=new b2CircleShape(r/worldScale);
			var fixtureDef:b2FixtureDef=new b2FixtureDef();
			fixtureDef.shape=circleShape;
			fixtureDef.density=1;
			fixtureDef.restitution=0.4;
			fixtureDef.friction=0.5;
			var theSphere:b2Body=world.CreateBody(bodyDef);
			theSphere.CreateFixture(fixtureDef);
			return theSphere;
		}
		private function floor():void {
			var bodyDef:b2BodyDef=new b2BodyDef();
			bodyDef.position.Set(320/worldScale,465/worldScale);
			var polygonShape:b2PolygonShape=new b2PolygonShape();
			polygonShape.SetAsBox(320/worldScale,15/worldScale);
			var fixtureDef:b2FixtureDef=new b2FixtureDef();
			fixtureDef.shape=polygonShape;
			fixtureDef.restitution=0.4;
			fixtureDef.friction=0.5;
			var theFloor:b2Body=world.CreateBody(bodyDef);
			theFloor.CreateFixture(fixtureDef);
		}
		private function debugDraw():void {
			var debugDraw:b2DebugDraw=new b2DebugDraw();
			var debugSprite:Sprite=new Sprite();
			addChild(debugSprite);
			debugDraw.SetSprite(debugSprite);
			debugDraw.SetDrawScale(worldScale);
			debugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
			debugDraw.SetFillAlpha(0.5);
			world.SetDebugDraw(debugDraw);
		}
		private function updateWorld(e:Event):void {
			world.Step(1/30,10,10);
			world.ClearForces();
			world.DrawDebugData();
		}
	}
}

Ready to see the blob? here it is:

Unfortunately the blob is really rigid, because distance joints do not allow bodies to change the distance among them (that's why it's called "distance" joint).

5) Playing with damping and frequency

Too good we can play with distance joint damping and frequency to turn boring distance joints into happy springs:

package {
	import flash.display.Sprite;
	import flash.events.Event;
	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;
		private var worldScale:Number=30;
		private var sphereVector:Vector.;
		private var blobX:Number=320;
		private var blobY:Number=240;
		private var particleNumber:Number=16;
		private var particleDistance:Number=50;
		public function Main() {
			world=new b2World(new b2Vec2(0,10),true);
			debugDraw();
			floor();
			sphereVector=new Vector.();
			sphereVector.push(sphere(blobX,blobY,15));
			for (var i:Number=0; i0) {
					var distanceX:Number=posX/worldScale-sphereVector[sphereVector.length-2].GetPosition().x;
					var distanceY:Number=posY/worldScale-sphereVector[sphereVector.length-2].GetPosition().y;
					var distance:Number=Math.sqrt(distanceX*distanceX+distanceY*distanceY);
					dJoint.bodyA=sphereVector[sphereVector.length-2];
					dJoint.bodyB=sphereVector[sphereVector.length-1];
					dJoint.localAnchorA=new b2Vec2(0,0);
					dJoint.localAnchorB=new b2Vec2(0,0);
					dJoint.length=distance;
					distanceJoint=world.CreateJoint(dJoint) as b2DistanceJoint;
				}
				if (i==particleNumber-1) {
					distanceX=posX/worldScale-sphereVector[1].GetPosition().x;
					distanceY=posY/worldScale-sphereVector[1].GetPosition().y;
					distance=Math.sqrt(distanceX*distanceX+distanceY*distanceY);
					dJoint.bodyA=sphereVector[1];
					dJoint.bodyB=sphereVector[sphereVector.length-1];
					dJoint.localAnchorA=new b2Vec2(0,0);
					dJoint.localAnchorB=new b2Vec2(0,0);
					dJoint.length=distance;
					distanceJoint=world.CreateJoint(dJoint) as b2DistanceJoint;
				}
			}
			addEventListener(Event.ENTER_FRAME,updateWorld);
		}
		private function sphere(pX:int,pY:int,r:Number):b2Body {
			var bodyDef:b2BodyDef=new b2BodyDef();
			bodyDef.position.Set(pX/worldScale,pY/worldScale);
			bodyDef.type=b2Body.b2_dynamicBody;
			var circleShape:b2CircleShape;
			circleShape=new b2CircleShape(r/worldScale);
			var fixtureDef:b2FixtureDef=new b2FixtureDef();
			fixtureDef.shape=circleShape;
			fixtureDef.density=1;
			fixtureDef.restitution=0.4;
			fixtureDef.friction=0.5;
			var theSphere:b2Body=world.CreateBody(bodyDef);
			theSphere.CreateFixture(fixtureDef);
			return theSphere;
		}
		private function floor():void {
			var bodyDef:b2BodyDef=new b2BodyDef();
			bodyDef.position.Set(320/worldScale,465/worldScale);
			var polygonShape:b2PolygonShape=new b2PolygonShape();
			polygonShape.SetAsBox(320/worldScale,15/worldScale);
			var fixtureDef:b2FixtureDef=new b2FixtureDef();
			fixtureDef.shape=polygonShape;
			fixtureDef.restitution=0.4;
			fixtureDef.friction=0.5;
			var theFloor:b2Body=world.CreateBody(bodyDef);
			theFloor.CreateFixture(fixtureDef);
		}
		private function debugDraw():void {
			var debugDraw:b2DebugDraw=new b2DebugDraw();
			var debugSprite:Sprite=new Sprite();
			addChild(debugSprite);
			debugDraw.SetSprite(debugSprite);
			debugDraw.SetDrawScale(worldScale);
			debugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
			debugDraw.SetFillAlpha(0.5);
			world.SetDebugDraw(debugDraw);
		}
		private function updateWorld(e:Event):void {
			world.Step(1/30,10,10);
			world.ClearForces();
			world.DrawDebugData();
		}
	}
}

And finally here it is:

Now the distance joints are not that rigid, and playing with dampingRatio and frequencyHz properties (recommended range respectively 0 to 1 and 1 to 30) you can achieve a good blob effect.

I am making a game out of it, stay tuned.

Download the source code.

Last minute edit: my Box2D book is expected to be on the shelves on October 21.

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