Get the full commented source code of

HTML5 Suika Watermelon Game

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.