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.