Talking about Shrink it game, Actionscript 3, Box2D, Flash and Game development.
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:
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.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.