Step by step creation of a Box2D soft body blob
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:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
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.<b2Body>; 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.<b2Body>(); sphereVector.push(sphere(blobX,blobY,15)); for (var i:Number=0; i<particleNumber; i++) { var angle:Number=(2*Math.PI)/particleNumber*i; var posX:Number=blobX+particleDistance*Math.cos(angle); var posY:Number=blobY+particleDistance*Math.sin(angle); sphereVector.push(sphere(posX,posY,2)); } 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(); } } } |
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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
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.<b2Body>; 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.<b2Body>(); sphereVector.push(sphere(blobX,blobY,15)); for (var i:Number=0; i<particleNumber; i++) { var angle:Number=(2*Math.PI)/particleNumber*i; var posX:Number=blobX+particleDistance*Math.cos(angle); var posY:Number=blobY+particleDistance*Math.sin(angle); sphereVector.push(sphere(posX,posY,2)); var dJoint:b2DistanceJointDef=new b2DistanceJointDef(); dJoint.bodyA=sphereVector[0]; dJoint.bodyB=sphereVector[sphereVector.length-1]; dJoint.localAnchorA=new b2Vec2(0,0); dJoint.localAnchorB=new b2Vec2(0,0); dJoint.length=particleDistance/worldScale; var distanceJoint:b2DistanceJoint; 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(); } } } |
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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
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.<b2Body>; 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.<b2Body>(); sphereVector.push(sphere(blobX,blobY,15)); for (var i:Number=0; i<particleNumber; i++) { var angle:Number=(2*Math.PI)/particleNumber*i; var posX:Number=blobX+particleDistance*Math.cos(angle); var posY:Number=blobY+particleDistance*Math.sin(angle); sphereVector.push(sphere(posX,posY,2)); var dJoint:b2DistanceJointDef=new b2DistanceJointDef(); dJoint.bodyA=sphereVector[0]; dJoint.bodyB=sphereVector[sphereVector.length-1]; dJoint.localAnchorA=new b2Vec2(0,0); dJoint.localAnchorB=new b2Vec2(0,0); dJoint.length=particleDistance/worldScale; var distanceJoint:b2DistanceJoint; distanceJoint=world.CreateJoint(dJoint) as b2DistanceJoint; if (i>0) { 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:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
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.<b2Body>; 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.<b2Body>(); sphereVector.push(sphere(blobX,blobY,15)); for (var i:Number=0; i<particleNumber; i++) { var angle:Number=(2*Math.PI)/particleNumber*i; var posX:Number=blobX+particleDistance*Math.cos(angle); var posY:Number=blobY+particleDistance*Math.sin(angle); sphereVector.push(sphere(posX,posY,2)); var dJoint:b2DistanceJointDef=new b2DistanceJointDef(); dJoint.bodyA=sphereVector[0]; dJoint.bodyB=sphereVector[sphereVector.length-1]; dJoint.localAnchorA=new b2Vec2(0,0); dJoint.localAnchorB=new b2Vec2(0,0); dJoint.length=particleDistance/worldScale; var distanceJoint:b2DistanceJoint; distanceJoint=world.CreateJoint(dJoint) as b2DistanceJoint; if (i>0) { 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:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
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.<b2Body>; 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.<b2Body>(); sphereVector.push(sphere(blobX,blobY,15)); for (var i:Number=0; i<particleNumber; i++) { var angle:Number=(2*Math.PI)/particleNumber*i; var posX:Number=blobX+particleDistance*Math.cos(angle); var posY:Number=blobY+particleDistance*Math.sin(angle); sphereVector.push(sphere(posX,posY,2)); var dJoint:b2DistanceJointDef=new b2DistanceJointDef(); dJoint.bodyA=sphereVector[0]; dJoint.bodyB=sphereVector[sphereVector.length-1]; dJoint.localAnchorA=new b2Vec2(0,0); dJoint.localAnchorB=new b2Vec2(0,0); dJoint.length=particleDistance/worldScale; dJoint.dampingRatio=0.5; dJoint.frequencyHz=5; var distanceJoint:b2DistanceJoint; distanceJoint=world.CreateJoint(dJoint) as b2DistanceJoint; if (i>0) { 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.
Last minute edit: my Box2D book is expected to be on the shelves on October 21.
They can be easily customized to meet the unique requirements of your project.














This post has 13 comments
Zerk
nice lesson )
But, how to tie sprite with this body?
Danny Birch
This is awesome, I am also making a game using a similar blob kind effect. Originally I was trying to stick individual blobs together but this has a really good feel to it.
Nice!
Sumendra
This is flash version. Is this soft body implementation possible in box2dweb ? I have make games in box2dweb (http://pixsansar.com/cool-box2dweb-games) but now want to make some real games using this new idea.
Shawn
Thanks for the awesome tutorial! One suggestion would be to only show the new code at each step, or at least highlight it. Would make it much easier to follow each step.
Jason
I suggest using squares for the outside instead of circles; it will reduce friction and make it more realistic.
Paul Smith
This is brilliant, just how on earth do you texture it? You must need some opengl/directx filter that blends a bunch of mini circle blob textures into one big blob.
Quagh
Simple, but effective. Try creating a second row of outer rings and joining them inversely with the inner ring to create a strong (yet flexible) blob, one that nearly cannot be squashed (doesn’t get stuck on corners and doesn’t get objects stuck inside). Out of the 7 blob types I tested, that one seems the most strong.
A (simple) way to represent this graphically is to draw lines to each outer circle, close the connection and fill it. Just use the default Flash graphics (line + fill).
ricar
but it seems that nape has soft bodies
ricar
what about nape it has soft bodies
http://www.newgrounds.com/dump/item/b3000c07f9d39a44d0e4842c5ad8b057
http://flashgamedojo.com/wiki/index.php?title=Nape#Demos
Marcelo de Assis
How can we stop that “shaking”, that non-stoping movement?
Janitha
Hi Emanuel,
I’m trying to get my blob to nicely roll over a terrain as it moves along it left to right. The linear movement is alright and the center body also rotates fine but the whole body does not rotate, just the center body. How do I get to make it work as if the blob is rolling?
Thanks
PRABHAKARAN
Nice how can i add my texture to this blob,could you help me
arjun
Tutorial is helpful, but can u add sprite to blob and post again.Thanks