Shrink it Box2D prototype – Step 2
In the previous step I showed you how to shrink/expand any kind of polygon.
Anyway in the original game, you can’t expand objects as much as you want, because you need mass to do it, and this adds strategy to the gameplay, because you have to shrink objects to gather the necessary mass to expand other ones.
I suppose the required amount of mass is the difference between the final and the initial mass.
At the same time, when we shrink an object, we’ll gather mass… probably determined by the difference between the initial and the final mass.
When you want to play with masses, the first thing you must setup carefully is the expand/shrink ratio. In the previous step I used a 10% for both shrinking and expanding.
This leads to a glitchy gameplay because if I have a sphere with a radius = 100 and I shrink it by 10% I get a sphere with a radius = 90. But if I expand 90 by 10% I get a sphere with a radius = 99. I don’t get the original sphere. So I cannot use these values, because I need to get the initial object if I expand it and then shrink it or if I shrink it and then expand it.
So in this example I am using 20% shrinking and 25% expanding. This way, our object with radius = 100 shrinked by 20% will have a radius of 80… and a radius = 80 expanded by 25% returns 100 again.
It’s up to you to find a couple of compatible numbers.
About masses, this is the concept: when the player shrinks an object, there isn’t any problem, I just have to compare the old mass with the new one and add the difference to the available mass. To get a body’s mass, use GetMass().
When the player tries to expand an objects, things become a little harder because we can’t know the future mass of the body, unless we want to heavily play with geometry.
So we have to make a little trick: first we remove the original shape, then we create and attach the new, expanded shape to the body, so we can get the mass of the final body. If we have enough mass, then we render the body, otherwise we remove the new shape and restore the old one. Since we do everything before rendering the frame, it will work nicely.
This is the 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 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 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 | package { import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.events.KeyboardEvent; import flash.text.TextField; import Box2D.Dynamics.*; import Box2D.Collision.*; import Box2D.Collision.Shapes.*; import Box2D.Common.Math.*; public class shrink extends Sprite { var body:b2Body; public var m_world:b2World; public var m_iterations:int=10; public var m_timeStep:Number=1.0/30.0; public var mousePVec:b2Vec2 = new b2Vec2(); // variable to store if the player is pressing SPACE public var space_pressed:Boolean=false; // initial available mass public var avail_mass:Number=20; // text field to display available mass var text_field:TextField = new TextField(); public function shrink() { var worldAABB:b2AABB = new b2AABB(); var bodyDef:b2BodyDef = new b2BodyDef(); var polygon:b2PolygonDef = new b2PolygonDef(); var circleDef:b2CircleDef= new b2CircleDef(); worldAABB.lowerBound.Set(-100.0, -100.0); worldAABB.upperBound.Set(100.0, 100.0); m_world=new b2World(worldAABB,new b2Vec2(0,10),true); // debug draw start var m_sprite:Sprite; m_sprite = new Sprite(); addChild(m_sprite); var dbgDraw:b2DebugDraw = new b2DebugDraw(); var dbgSprite:Sprite = new Sprite(); m_sprite.addChild(dbgSprite); dbgDraw.m_sprite=m_sprite; dbgDraw.m_drawScale=30; dbgDraw.m_alpha=1; dbgDraw.m_fillAlpha=0.5; dbgDraw.m_lineThickness=1; dbgDraw.m_drawFlags=b2DebugDraw.e_shapeBit; m_world.SetDebugDraw(dbgDraw); // debug draw end // ground bodyDef.position.Set(10, 12); polygon.SetAsBox(30, 3); polygon.density=0; polygon.friction=0.3; polygon.restitution=0.2; body=m_world.CreateBody(bodyDef); body.CreateShape(polygon); body.SetMassFromShapes(); // circle bodyDef.position.Set(3,5); circleDef.radius=2; circleDef.density=1; circleDef.friction=0.5; circleDef.restitution=0.2; body=m_world.CreateBody(bodyDef); body.CreateShape(circleDef); body.SetMassFromShapes(); // box bodyDef.position.Set(13, 5); polygon.SetAsBox(2, 2); polygon.density=1; polygon.friction=0.5; polygon.restitution=0.2; body=m_world.CreateBody(bodyDef); body.CreateShape(polygon); body.SetMassFromShapes(); // triangle bodyDef.position.Set(13,3); polygon.vertexCount=3; polygon.vertices[0].Set(0,-2); polygon.vertices[1].Set(2,2); polygon.vertices[2].Set(-2,2); polygon.density=1; polygon.friction=0.5; polygon.restitution=0.2; body=m_world.CreateBody(bodyDef); body.CreateShape(polygon); body.SetMassFromShapes(); // custom shape bodyDef.position.Set(8,4); polygon.vertexCount=5; polygon.vertices[0].Set(0,-2); polygon.vertices[1].Set(2,0); polygon.vertices[2].Set(1,2); polygon.vertices[3].Set(-1,2); polygon.vertices[4].Set(-2,0); polygon.density=1; polygon.friction=0.5; polygon.restitution=0.2; body=m_world.CreateBody(bodyDef); body.CreateShape(polygon); body.SetMassFromShapes(); // addChild(text_field); text_field.text="Available mass: "+avail_mass.toString(); text_field.y=330; text_field.x=20; text_field.width=300; // addEventListener(Event.ENTER_FRAME, Update, false, 0, true); stage.addEventListener(KeyboardEvent.KEY_DOWN, key_down); stage.addEventListener( KeyboardEvent.KEY_UP, key_up); stage.addEventListener(MouseEvent.MOUSE_DOWN, GetBodyAtMouse); } // detecting if the player pressed SPACE public function key_down(event:KeyboardEvent):void { if (event.keyCode==32) { space_pressed=true; } } // detecting if the player released SPACE public function key_up(event:KeyboardEvent):void { if (event.keyCode==32) { space_pressed=false; } } // public function GetBodyAtMouse(e:MouseEvent):b2Body { // scale multiplyers. Remember to choose compatible multipliers // in this case, 0.8*1.25=1 => compatible // 0.9*1.1=0.99 => not compabile. Must be 1 var mult:Number=0.8; if (space_pressed) { mult=1.25; } var mouseXWorldPhys = (mouseX)/30; var mouseYWorldPhys = (mouseY)/30; mousePVec.Set(mouseXWorldPhys, mouseYWorldPhys); var aabb:b2AABB = new b2AABB(); aabb.lowerBound.Set(mouseXWorldPhys - 0.001, mouseYWorldPhys - 0.001); aabb.upperBound.Set(mouseXWorldPhys + 0.001, mouseYWorldPhys + 0.001); var k_maxCount:int=10; var shapes:Array = new Array(); var count:int=m_world.Query(aabb,shapes,k_maxCount); var body:b2Body=null; for (var i:int = 0; i < count; ++i) { var tShape:b2Shape=shapes[i] as b2Shape; var inside:Boolean=tShape.TestPoint(tShape.GetBody().GetXForm(),mousePVec); if (inside) { body=tShape.GetBody(); break; } } // if I selected a STATIC body... if (body&&! body.IsStatic()) { // gettinc current mass var cur_mass:Number=body.GetMass(); // variable to store new shape's mass var new_mass:Number; var s:b2Shape=body.GetShapeList(); var type:int=s.GetType(); switch (type) { case 0 : // I know it's a circle, so I am creating a b2CircleShape variable var circle:b2CircleShape=body.GetShapeList() as b2CircleShape; // getting the radius.. var r=circle.GetRadius(); // removing the circle shape from the body body.DestroyShape(circle); // creating a new circle shape var circleDef:b2CircleDef; circleDef = new b2CircleDef(); // calculating new radius circleDef.radius=r*mult; circleDef.density=1.0; circleDef.friction=0.5; circleDef.restitution=0.2; // attach the shape to the body body.CreateShape(circleDef); // determine new body mass body.SetMassFromShapes(); // determining new mass new_mass=body.GetMass(); // calculating available mass after scaling avail_mass += (cur_mass-new_mass); // if there isn't enough mass... if (avail_mass<0) { // remove new circle shape and restore last used circle shape avail_mass+=new_mass-cur_mass; circle=body.GetShapeList() as b2CircleShape; body.DestroyShape(circle); circleDef = new b2CircleDef(); circleDef.radius=r; circleDef.density=1.0; circleDef.friction=0.5; circleDef.restitution=0.2; body.CreateShape(circleDef); body.SetMassFromShapes(); } // displaying mass text_field.text="Available mass: "+avail_mass.toString(); break; case 1 : // now I know it's a polygon var poly:b2PolygonShape=body.GetShapeList() as b2PolygonShape; // UNIVERSAL POLYGON SCALING ROUTINE THANX TO ILYA var vertex_num:int=poly.GetVertexCount(); var vertex_array:Array=poly.GetVertices(); for each (var vert:b2Vec2 in vertex_array) { vert.Multiply(mult); } body.DestroyShape(poly); var new_shape:b2PolygonDef = new b2PolygonDef(); new_shape.vertexCount=vertex_num; new_shape.vertices=vertex_array; new_shape.friction=0.5; new_shape.density=1; new_shape.restitution=0.2; body.CreateShape(new_shape); body.SetMassFromShapes(); // determining new mass new_mass=body.GetMass(); // calculating available mass after scaling avail_mass+=cur_mass-new_mass; // if there isn't enough mass... if (avail_mass<0) { // remove new polygon shape and restore last used polygon shape avail_mass+=new_mass-cur_mass; poly=body.GetShapeList() as b2PolygonShape; body.DestroyShape(poly); for each (vert in vertex_array) { vert.Multiply(1/mult); } new_shape = new b2PolygonDef(); new_shape.vertexCount=vertex_num; new_shape.vertices=vertex_array; new_shape.friction=0.5; new_shape.density=1; new_shape.restitution=0.2; body.CreateShape(new_shape); body.SetMassFromShapes(); } // displaying mass text_field.text="Available mass: "+avail_mass.toString(); break; } } return body; } public function Update(e:Event):void { m_world.Step(m_timeStep, m_iterations); } } } |
And this is the result:
Click on a body to shrink it, click + SPACE to enlarge it, and look at your mass meter in the lower left corner.
No need to download, simply copy/paste this code in the file you can find in the previous step.
Next time, we’ll add the final gameplay.
They can be easily customized to meet the unique requirements of your project.

























This post has one comment
Mike
Hi Emanuele
Really liked this tutorial. I was looking for a way to resize box2d objects on the fly and this solved the problem.
Take a look at my game using this techniques
http://apps.facebook.com/bookpileup
Thanks
Mike