Get the full commented source code of

HTML5 Suika Watermelon Game

Talking about Actionscript 3, Box2D and Flash.

In the second step of this tutorial we’ll see how to turn the terrain created with geometry in step 1 into a real Box2D terrain.

I am going to use b2Separator class By Antoan Angelov to render any type of polygon with Box2D.

This will allow you to quickly render any kind of polygon but won’t save us from the infamous hole bug. This happens when one or more points of the “circle” which will represents the hole lie on a segment of a polygon:

I (obviously) found a solution for this bug, and I am going to show you the final result, next time, meanwhile have a look at this step, here is the source code with every line commented and highlights on new pieces of code.

package {
	import flash.display.Sprite;
	import flash.geom.Point;
	import flash.events.MouseEvent;
	import flash.events.Event;
	import com.logicom.geom.Clipper;
	import com.logicom.geom.ClipType;
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	import Box2DSeparator.*;
	public class Main extends Sprite {
		// the canvas where we'll draw the terrain
		private var terrainCanvas:Sprite=new Sprite();
		// the array of polygons forming the terrain
		private var terrainPolygons:Array=new Array();
		// Box2D world
		private var world:b2World=new b2World(new b2Vec2(0,5),true);
		// old worldScale conversion
		private var worldScale:Number=30;
		public function Main():void {
			addChild(terrainCanvas);
			// creation of a 13x8 grid of squares, this will be our terrain
			for (var i:Number=0; i<13; i++) {
				for (var j:Number=0; j<8; j++) {
					var thePoly:Array = new Array(new Point(-5+i*50,80+j*50),new Point(45+i*50,80+j*50),new Point(45+i*50,130+j*50),new Point(-5+i*50,130+j*50));
					terrainPolygons.push(thePoly);
				}
			}
			// drawing the terrain
			drawTerrain();
			// placing debug draw over the terrain, so you can see if geometry and physics terrain match
			debugDraw();
			// listeners: basically we destroy the terrain with a mouse click or a mouse drag
			stage.addEventListener(MouseEvent.MOUSE_DOWN,function(){stage.addEventListener(MouseEvent.MOUSE_MOVE,doExplosion)});
			stage.addEventListener(MouseEvent.MOUSE_UP,function(){stage.removeEventListener(MouseEvent.MOUSE_MOVE,doExplosion)});
			stage.addEventListener(MouseEvent.CLICK,doExplosion);
			//
			addEventListener(Event.ENTER_FRAME,update);
		}
		// the core of the script, doExplosion function
		private function doExplosion(e:MouseEvent):void {
			// first we are going to remove all "rock" bodies. This can be optimized by removing only
			// bodies affected by the explosion
			for (var currentBody:b2Body=world.GetBodyList(); currentBody; currentBody=currentBody.GetNext()) {
				if (currentBody.GetUserData()&&currentBody.GetUserData()=="rock") {
					world.DestroyBody(currentBody);
				}
			}
			// creation of an explosion polygon, looking like a circle, obviously it can be any shape you want
			var explosionPolygon:Array=createCircle(20,new Point(mouseX,mouseY),30);
			// for each existing terrain polygon, check the difference between the polygon itself and the 
			// explosion polygon. This should be optimized in some way, checking only for terrain polygons
			// which are actually affected by the explosion.
			// Then we remove the terrain polygon from the array, and we add the resulting polygon(s) after
			// difference is calculated.
			for (var i:Number=terrainPolygons.length-1; i>=0; i--) {
				var resultPolygons:Array=Clipper.clipPolygon(terrainPolygons[i],explosionPolygon,ClipType.DIFFERENCE);
				var totalArea:Number=0;
				terrainPolygons.splice(i,1);
				for (var j:Number=0; j<resultPolygons.length; j++) {
					terrainPolygons.push(resultPolygons[j]);
				}

			}
			// now it's time to redraw the terrain
			drawTerrain();
		}
		// function to create a "circular" polygon
		private function createCircle(precision:Number,origin:Point,radius:Number):Array {
			var angle:Number=2*Math.PI/precision;
			var circleArray:Array=new Array();
			for (var i:Number=0; i<precision; i++) {
				circleArray.push(new Point(origin.x+radius*Math.cos(angle*i),origin.y+radius*Math.sin(angle*i)));
			}
			return circleArray;
		}
		// function to create a body from a polygon
		private function createBody(a:Array):void {
			// using b2Separator class. Will need optimization to avoid the infamous hole error
			var sep:b2Separator = new b2Separator();
			// creation of the body definition
			var bodyDef:b2BodyDef = new b2BodyDef();
			// we always place the body at (0,0) then adjust fixture position
			bodyDef.position.Set(0,0);
			// some userData stuff to let the world know we are dealing with the terrain
			bodyDef.userData="rock";
			// body creation
			var body:b2Body=world.CreateBody(bodyDef);
			// fixture definition, this is where b2Separator comes into play
			var fixtureDef:b2FixtureDef = new b2FixtureDef();
			fixtureDef.restitution=0.5;
			fixtureDef.friction=0;
			// now we create a vector and we place inside the vector all points we found in a array
			// translated into their b2Vec2 counterparts
			var vec:Vector.<b2Vec2> = new Vector.<b2Vec2>();
			for (var i:Number=0; i<a.length; i++) {
				vec.push(new b2Vec2(a[i].x/worldScale,a[i].y/worldScale));
			}
			// finally we call Separate method
			sep.Separate(body,fixtureDef,vec,30);
		}
		// these remaining functions are not interesting, they just display the terrain
		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 update(e:Event):void {
			world.Step(1/30,10,10);
			world.ClearForces();
			world.DrawDebugData();
		}
		private function drawTerrain() {
			terrainCanvas.graphics.clear();
			for (var i:Number=0; i<terrainPolygons.length; i++) {
				drawPolygon(terrainPolygons[i],terrainCanvas,0x0000FF);
				createBody(terrainPolygons[i]);
			}
		}
		private function drawPolygon(polygon:Array,canvas:Sprite,color:Number):void {
			canvas.graphics.lineStyle(0.1,0xffffff);
			canvas.graphics.beginFill(color);
			var n:uint=polygon.length;
			if (n<3) {
				return;
			}
			var p:Point=polygon[0];
			canvas.graphics.moveTo(p.x, p.y);
			for (var i:Number = 1; i <= n; ++i) {
				p=polygon[i%n];
				canvas.graphics.lineTo(p.x, p.y);
			}
		}
	}
}

And this is the result:

Click or drag on the terrain to create a hole or dig. Watch out for the infamous bug.

Download the source code. Next time, the final prototype.

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