Do you like my tutorials?

Then consider supporting me on Ko-fi

Talking about Pop to Save game, Actionscript 3, Box2D, Flash and Game development.

During these days I am playing a nice free physics iOS game called Pop to Save developed by Cetin Caglar.

The game combines physics with line drawing and erasing, like Way of an Idea, but enriches the gameplay with particles.

Since Way of an Idea prototype has already been discussed in this post, with some easy changes to the code we can have a working prototype of Pop to Save, try it by yourself:

You have to repeatedly hit the orange receiver with the spheres generated by the blue emitter. The receiver will absorb spheres until it explodes releasing them, and another emitter is randomly created.

MOUSE OVER the emitter to shoot spheres

CLICK AND DRAG mouse pointer to draw terrain

MOUSE OVER the terrain while keeping SPACEBAR pressed to delete terrain.

To make things easier and avoid unnecessary tasks, I am not using Box2D contact listeners to check for collisions, but only some trigonometry.

Here is the commented script, uncommented parts are taken from the original post so it won’t be a problem for you to figure out how it works.

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.events.KeyboardEvent;
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	public class Main extends Sprite {
		private var drawing:Boolean=false;
		private var canvas:Sprite = new Sprite();
		private var pointsArray:Array;
		private var pixelDist:int=20;
		private var savedX:int;
		private var savedY:int;
		private var world:b2World=new b2World(new b2Vec2(0,10),true);
		private var worldScale:Number=30;
		private var debugSprite:Sprite=new Sprite();
		private var degToRad:Number=0.0174532925;
		// bubbles emitter
		private var emitter:Emitter=new Emitter();
		private var emitterForce:Number=50;				// force applied to generated spheres
		private var emitterAngle:Number=-45;			// emitter angle
		private var fire:Boolean=false;					// should the emitter fire?
		private var fireRatio:Number=3;					// fire ratio, 3 means "one each 3 frames"
		private var lastFire:Number=3;					// dummy variable to tell us the last time we fired
		// bubbles receiver
		private var receiver:Receiver=new Receiver();
		public function Main() {
			// adding the emitter
			addChild(emitter);
			emitter.x=60;
			emitter.y=240;
			emitter.rotation=emitterAngle;
			// placing the receiver at a random position
			addChild(receiver);
			receiver.x=Math.random()*400+200;
			receiver.y=Math.random()*250+160;
			addChild(canvas);
			canvas.graphics.lineStyle(5);
			stage.addEventListener(MouseEvent.MOUSE_DOWN,mousePressed);
			stage.addEventListener(MouseEvent.MOUSE_MOVE,mouseMoved);
			stage.addEventListener(MouseEvent.MOUSE_UP,mouseReleased);
			addEventListener(Event.ENTER_FRAME,update);
			stage.addEventListener(KeyboardEvent.KEY_DOWN,keyPressed);
			// emitter fires on mouse over
			emitter.addEventListener(MouseEvent.MOUSE_OVER,emitterFire);
			addChild(debugSprite);
			debugDraw();
		}

		private function emitterFire(e:MouseEvent):void {
			// the emitter fires
			fire=true;
			emitter.removeEventListener(MouseEvent.MOUSE_OVER,emitterFire);
			emitter.addEventListener(MouseEvent.MOUSE_OUT,stopEmitterFire);
		}

		private function stopEmitterFire(e:MouseEvent):void {
			// the emitter stops firing
			fire=false;
			emitter.addEventListener(MouseEvent.MOUSE_OVER,emitterFire);
			emitter.removeEventListener(MouseEvent.MOUSE_OUT,stopEmitterFire);
		}

		private function mousePressed(e:MouseEvent):void {
			drawing=true;
			canvas.graphics.moveTo(mouseX,mouseY);
			pointsArray=new Array();
			savedX=mouseX;
			savedY=mouseY;
			pointsArray.push(savedX);
			pointsArray.push(savedY);
		}
		
		private function mouseMoved(e:MouseEvent):void {
			if (drawing) {
				var distX:int=mouseX-savedX;
				var distY:int=mouseY-savedY;
				if (distX*distX+distY*distY>pixelDist*pixelDist) {
					canvas.graphics.lineTo(mouseX,mouseY);
					savedX=mouseX;
					savedY=mouseY;
					pointsArray.push(savedX);
					pointsArray.push(savedY);
				}
			}
		}
		
		private function mouseReleased(e:MouseEvent):void {
			drawing=false;
			var sx:int;
			var ex:int;
			var sy:int;
			var ey:int;
			var distX:int;
			var distY:int;
			var dist:Number;
			var angle:Number;
			var segments:int=pointsArray.length/2-1;
			for (var i:int=0; i<segments; i++) {
				sx=pointsArray[i*2];
				sy=pointsArray[i*2+1];
				ex=pointsArray[i*2+2];
				ey=pointsArray[i*2+3];
				distX=sx-ex;
				distY=sy-ey;
				dist=Math.sqrt(distX*distX+distY*distY);
				angle=Math.atan2(distY,distX);
				addPath((sx+ex)/2,(sy+ey)/2,Math.abs(dist),4,angle);
			}
			canvas.graphics.clear();
			canvas.graphics.lineStyle(5);
		}
		
		private function keyPressed(event:KeyboardEvent):void {
			// deleting the path using the mouse while pressing SPACE key
			if (event.keyCode==32) {
				world.QueryPoint(queryCallback,new b2Vec2(mouseX/worldScale,mouseY/worldScale));
			}
		}

		private function addPath(pX:Number,pY:Number,w:Number,h:Number,angle:Number):void {
			var bodyDef:b2BodyDef= new b2BodyDef();
			var polygonShape:b2PolygonShape = new b2PolygonShape();
			polygonShape.SetAsOrientedBox(w/2/worldScale, h/2/worldScale, new b2Vec2(pX/worldScale,pY/worldScale),angle);
			var fixtureDef:b2FixtureDef = new b2FixtureDef();
			fixtureDef.density=1;
			fixtureDef.friction=0.5;
			fixtureDef.restitution=0.5;
			fixtureDef.shape=polygonShape;
			var body:b2Body=world.CreateBody(bodyDef);
			body.CreateFixture(fixtureDef);
		}

		private function queryCallback(fixture:b2Fixture):Boolean {
			var touchedBody:b2Body=fixture.GetBody();
			for (var f:b2Fixture=touchedBody.GetFixtureList(); f; f=f.GetNext()) {
				if (touchedBody.GetType()==b2Body.b2_staticBody) {
					world.DestroyBody(touchedBody);
				}
			}
			return false;
		}

		private function fireParticle(pX:Number,pY:Number,shoot:Boolean):void {
			// particle generation
			var bodyDef:b2BodyDef= new b2BodyDef();
			bodyDef.type=b2Body.b2_dynamicBody;
			bodyDef.position.Set(pX/worldScale,pY/worldScale);
			var circleShape:b2CircleShape = new b2CircleShape(5/worldScale);
			var fixtureDef:b2FixtureDef = new b2FixtureDef();
			fixtureDef.density=1;
			fixtureDef.friction=0.5;
			fixtureDef.restitution=0.5;
			fixtureDef.shape=circleShape;
			var body:b2Body=world.CreateBody(bodyDef);
			body.CreateFixture(fixtureDef);
			// if needed, apply a force to the particle
			if (shoot) {
				body.ApplyForce(new b2Vec2(emitterForce*Math.cos(emitter.rotation*degToRad),emitterForce*Math.sin(emitter.rotation*degToRad)),body.GetWorldCenter());
			}
		}

		private function update(e:Event):void {
			// updating fire counter
			if (lastFire<fireRatio) {
				lastFire++;
			}
			// should we fire?
			if (fire && lastFire==fireRatio) {
				fireParticle(emitter.x,emitter.y,true);
				lastFire=0;
			}
			world.Step(1/30,10,10);
			world.ClearForces();
			for (var b:b2Body = world.GetBodyList(); b; b = b.GetNext()) {
				if (b.GetType()==b2Body.b2_dynamicBody) {
					// removing particles falling off the screen
					if (b.GetPosition().y*worldScale>480) {
						world.DestroyBody(b);
					}
					// distance between the particle and the receiver
					var distX:Number=b.GetPosition().x*worldScale-receiver.x;
					var distY:Number=b.GetPosition().y*worldScale-receiver.y;
					var distance:Number=distX*distX+distY*distY;
					// if less than receiver radius...
					if (distance<900) {
						// draining receiver "energy"
						receiver.alpha-=0.05;
						// once receiver energy reaches zero...
						if (receiver.alpha<=0) {
							// releasing captured particles
							for (var i:Number=0; i<20; i++) {
								fireParticle(receiver.x,receiver.y,false);
							}
							// moving the receiver
							receiver.alpha=1;
							receiver.x=Math.random()*400+200;
							receiver.y=Math.random()*250+160;
						}
						// removing the particle
						world.DestroyBody(b);
					}
				}
			}
			world.DrawDebugData();
		}

		private function debugDraw():void {
			var debugDraw:b2DebugDraw=new b2DebugDraw();
			debugDraw.SetSprite(debugSprite);
			debugDraw.SetDrawScale(worldScale);
			debugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
			debugDraw.SetFillAlpha(0.5);
			world.SetDebugDraw(debugDraw);
		}

	}

}

If you have any question, feel free to leave a comment, meanwhile download the source code and get the original game for free.

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