Talking about Angry Birds game, Actionscript 3, Box2D, Flash and Game development.
Do you know why Angry Birds had such as a worldwide success? Because people love to kill pigs in videogames.
I got sick when I saw Babe movie and I want to kill pigs since then.
Today, you will learn how to do it thanks to the second part of the Angry Birds tutorial.
Before we start, let me explain how we are going to kill them. A pig dies when something, including the bird, the objects in the game, and even the ground, hits it too hard.
Various objects in the game also break when they hit something too hard.
You are already able to see when two objects collide thanks to tutorials like Create a Flash prototype of The Moops – Combos of Joy, but we’ve never seen how to determine the force of the collision.
You still have to create a custom contact listener class, but rather than using BeginContact
function which just has an argument we can use to determine which objects collided, we will use PostSolve
, which has another argument, a b2ContactImpulse
variable.
With b2ContactImpulse
we can know the amount of impulse Box2D has to give to bodies to avoid them from penetrating each other due to the collision. In other words, the collision force.
Once we know the strength of the collision, we can compare it with the maximum strength allowed before an object breaks (which can also be different according to the material, such as glass, wood, stone) and if the collision will break the object, we simply remove it from the Box2D world, and then we’ll show a cute animation.
This is what I made with this concept:
Throw the pigeon to the castle on the right to see how all blocks (and the pig) which are hit hard get destroyed.
In this prototype, world boundaries and the bird itself can’t be destroyed.
This is the Main class:
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import Box2D.Dynamics.*;
import Box2D.Collision.*;
import Box2D.Collision.Shapes.*;
import Box2D.Common.Math.*;
import Box2D.Dynamics.Joints.*;
public class Main extends Sprite {
private var world:b2World=new b2World(new b2Vec2(0,10),true);
private var worldScale:int=30;
private var bird:birdMc=new birdMc();
private var birdSphere:b2Body;
private var customContact=new customContactListener();
public function Main() {
world.SetContactListener(customContact);
var bg:backgroundMc=new backgroundMc();
addChild(bg);
addChild(bird);
debugDraw();
bird.x=170;
bird.y=270;
bird.buttonMode=true;
addWall(320,10,320,395);
addWall(320,10,320,-5);
addWall(10,320,-5,240);
addWall(10,320,645,240);
for (var i:int=0; i<=4; i++) {
addBlock(20,20,450,365-i*40);
addBlock(20,20,550,365-i*40);
}
addBlock(80,20,500,165);
addPig(20,500,125);
addEventListener(Event.ENTER_FRAME,updateWorld);
bird.addEventListener(MouseEvent.MOUSE_DOWN,birdClicked);
}
private function addWall(w,h,px,py):void {
var floorShape:b2PolygonShape = new b2PolygonShape();
floorShape.SetAsBox(w/worldScale,h/worldScale);
var floorFixture:b2FixtureDef = new b2FixtureDef();
floorFixture.density=0;
floorFixture.friction=10;
floorFixture.restitution=0.5;
floorFixture.shape=floorShape;
var floorBodyDef:b2BodyDef = new b2BodyDef();
floorBodyDef.position.Set(px/worldScale,py/worldScale);
floorBodyDef.userData={assetName:"wall",assetSprite:null,remove:false};
var floor:b2Body=world.CreateBody(floorBodyDef);
floor.CreateFixture(floorFixture);
}
private function addPig(r,px,py):void {
var pigShape:b2CircleShape=new b2CircleShape(r/worldScale);
var pigFixture:b2FixtureDef = new b2FixtureDef();
pigFixture.density=1;
pigFixture.friction=3;
pigFixture.restitution=0.1;
pigFixture.shape=pigShape;
var pigBodyDef:b2BodyDef = new b2BodyDef();
pigBodyDef.position.Set(px/worldScale,py/worldScale);
pigBodyDef.type=b2Body.b2_dynamicBody;
pigBodyDef.userData={assetName:"pig",assetSprite:null,remove:false};
var pigSphere:b2Body=world.CreateBody(pigBodyDef);
pigSphere.CreateFixture(pigFixture);
}
private function addBlock(w,h,px,py):void {
var blockShape:b2PolygonShape = new b2PolygonShape();
blockShape.SetAsBox(w/worldScale,h/worldScale);
var blockFixture:b2FixtureDef = new b2FixtureDef();
blockFixture.density=0.5;
blockFixture.friction=10;
blockFixture.restitution=0.1;
blockFixture.shape=blockShape;
var blockBodyDef:b2BodyDef = new b2BodyDef();
blockBodyDef.position.Set(px/worldScale,py/worldScale);
blockBodyDef.userData={assetName:"block",assetSprite:null,remove:false};
blockBodyDef.type=b2Body.b2_dynamicBody;
var block:b2Body=world.CreateBody(blockBodyDef);
block.CreateFixture(blockFixture);
}
private function birdClicked(e:MouseEvent):void {
addEventListener(MouseEvent.MOUSE_MOVE,birdMoved);
addEventListener(MouseEvent.MOUSE_UP,birdReleased);
bird.removeEventListener(MouseEvent.MOUSE_DOWN,birdClicked);
}
private function debugDraw():void {
var worldDebugDraw:b2DebugDraw=new b2DebugDraw();
var debugSprite:Sprite = new Sprite();
addChild(debugSprite);
worldDebugDraw.SetSprite(debugSprite);
worldDebugDraw.SetDrawScale(worldScale);
worldDebugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
worldDebugDraw.SetFillAlpha(0.8);
world.SetDebugDraw(worldDebugDraw);
}
private function birdMoved(e:MouseEvent):void {
bird.x=mouseX;
bird.y=mouseY;
var distanceX:Number=bird.x-170;
var distanceY:Number=bird.y-270;
if (distanceX*distanceX+distanceY*distanceY>10000) {
var birdAngle:Number=Math.atan2(distanceY,distanceX);
bird.x=170+100*Math.cos(birdAngle);
bird.y=270+100*Math.sin(birdAngle);
}
}
private function birdReleased(e:MouseEvent):void {
bird.buttonMode=false;
removeEventListener(MouseEvent.MOUSE_MOVE,birdMoved);
removeEventListener(MouseEvent.MOUSE_UP,birdReleased);
var sphereShape:b2CircleShape=new b2CircleShape(15/worldScale);
var sphereFixture:b2FixtureDef = new b2FixtureDef();
sphereFixture.density=1;
sphereFixture.friction=3;
sphereFixture.restitution=0.1;
sphereFixture.shape=sphereShape;
var sphereBodyDef:b2BodyDef = new b2BodyDef();
sphereBodyDef.type=b2Body.b2_dynamicBody;
sphereBodyDef.userData={assetName:"bird",assetSprite:bird,remove:false};
sphereBodyDef.position.Set(bird.x/worldScale,bird.y/worldScale);
birdSphere=world.CreateBody(sphereBodyDef);
birdSphere.CreateFixture(sphereFixture);
var distanceX:Number=bird.x-170;
var distanceY:Number=bird.y-270;
var distance:Number=Math.sqrt(distanceX*distanceX+distanceY*distanceY);
var birdAngle:Number=Math.atan2(distanceY,distanceX);
birdSphere.SetLinearVelocity(new b2Vec2(-distance*Math.cos(birdAngle)/4,-distance*Math.sin(birdAngle)/4));
}
private function updateWorld(e:Event):void {
world.Step(1/30,10,10);
for (var currentBody:b2Body=world.GetBodyList(); currentBody; currentBody=currentBody.GetNext()) {
if (currentBody.GetUserData()) {
if (currentBody.GetUserData().assetSprite!=null) {
currentBody.GetUserData().assetSprite.x=currentBody.GetPosition().x*worldScale;
currentBody.GetUserData().assetSprite.y=currentBody.GetPosition().y*worldScale;
currentBody.GetUserData().assetSprite.rotation=currentBody.GetAngle()*(180/Math.PI);
}
if (currentBody.GetUserData().remove) {
world.DestroyBody(currentBody);
}
}
}
world.ClearForces();
world.DrawDebugData();
}
}
}
and this is the custom contact listener:
package {
import Box2D.Dynamics.*;
import Box2D.Collision.*;
import Box2D.Dynamics.Contacts.*;
class customContactListener extends b2ContactListener {
private var MAX_IMPULSE:Number=10;
override public function PostSolve(contact:b2Contact, impulse:b2ContactImpulse):void {
if (impulse.normalImpulses[0]>MAX_IMPULSE) {
var nameA:String=contact.GetFixtureA().GetBody().GetUserData().assetName.toString();
var nameB:String=contact.GetFixtureB().GetBody().GetUserData().assetName.toString();
if (nameA!="wall" && nameA!="bird") {
contact.GetFixtureA().GetBody().GetUserData().remove=true;
}
if (nameB!="wall" && nameB!="bird") {
contact.GetFixtureB().GetBody().GetUserData().remove=true;
}
}
}
}
}
Next time, I’ll add some scrolling, and you’ll see how you can have a working prototype of Angry Birds in a bunch of lines.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.