Talking about Actionscript 3, Box2D and Flash.
In the second part of this series I showed you how to cut Box2D objects.
Unfortunately I was working in the debug draw environment, so the whole process can’t be applied in a real-world example, unless you want to publish a game with the debug draw graphics.
So it’s time to see how to cut your own sprites. This is what you’ll get at the end of this step:
Cut the debug draw polygons to see randomly colored debris fall down. These debris are Sprites generated in real time.
So let’s take a look at the source code, and see what changed since the previous step:
package {
import Box2D.Dynamics.*;
import Box2D.Collision.*;
import Box2D.Collision.Shapes.*;
import Box2D.Common.Math.*;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.events.Event;
public class Main extends Sprite {
private var world:b2World=new b2World(new b2Vec2(0,10),true);
private var worldScale:int=30;
private var debris:Sprite;
private var canvas:Sprite;
private var laserSegment:b2Segment;
private var drawing:Boolean=false;
private var affectedByLaser:Vector.;
private var entryPoint:Vector.;
public function Main() {
debugDraw();
addStuff();
debris = new Sprite();
addChild(debris);
canvas = new Sprite();
addChild(canvas);
addEventListener(Event.ENTER_FRAME, updateWorld);
stage.addEventListener(MouseEvent.MOUSE_DOWN,mousePressed);
stage.addEventListener(MouseEvent.MOUSE_MOVE,mouseMoved);
stage.addEventListener(MouseEvent.MOUSE_UP,mouseReleased);
}
private function mousePressed(e:MouseEvent):void {
drawing=true;
laserSegment=new b2Segment();
laserSegment.p1=new b2Vec2(mouseX/worldScale,mouseY/worldScale);
}
private function mouseMoved(e:MouseEvent):void {
if (drawing) {
canvas.graphics.clear();
canvas.graphics.lineStyle(1,0xff0000);
canvas.graphics.moveTo(laserSegment.p1.x*worldScale,laserSegment.p1.y*worldScale);
canvas.graphics.lineTo(mouseX,mouseY);
}
}
private function mouseReleased(e:MouseEvent):void {
drawing=false;
laserSegment.p2=new b2Vec2(mouseX/worldScale,mouseY/worldScale);
}
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 addStuff():void {
var floorBody:b2BodyDef= new b2BodyDef();
floorBody.position.Set(11,29);
floorBody.userData=new Object();
var floorShape:b2PolygonShape = new b2PolygonShape();
floorShape.SetAsBox(40,15);
var floorFixture:b2FixtureDef = new b2FixtureDef();
floorFixture.shape=floorShape;
var worldFloor:b2Body=world.CreateBody(floorBody);
worldFloor.CreateFixture(floorFixture);
//
var squareBody:b2BodyDef= new b2BodyDef();
squareBody.position.Set(16,5);
var squareShape:b2PolygonShape = new b2PolygonShape();
squareShape.SetAsBox(2.5,2.5);
var squareFixture:b2FixtureDef = new b2FixtureDef();
squareFixture.shape=squareShape;
var worldSquare:b2Body=world.CreateBody(squareBody);
worldSquare.CreateFixture(squareFixture);
//
var circleVector:Vector.=new Vector.();
var circleSteps:int=12;
var circleRadius:Number=3;
for (var i:int=0; i();
entryPoint=new Vector.();
world.RayCast(laserFired,laserSegment.p1,laserSegment.p2);
world.RayCast(laserFired,laserSegment.p2,laserSegment.p1);
laserSegment=null;
}
for (var currentBody:b2Body=world.GetBodyList(); currentBody; currentBody=currentBody.GetNext()) {
if (currentBody.GetUserData()!=null) {
currentBody.GetUserData().x=currentBody.GetPosition().x*worldScale;
currentBody.GetUserData().y=currentBody.GetPosition().y*worldScale;
currentBody.GetUserData().rotation=currentBody.GetAngle()*180/Math.PI;
}
}
world.DrawDebugData();
}
private function laserFired(fixture:b2Fixture,point:b2Vec2,normal:b2Vec2,fraction:Number):Number {
var affectedBody:b2Body=fixture.GetBody();
var affectedPolygon:b2PolygonShape=fixture.GetShape() as b2PolygonShape;
var fixtureIndex:int=affectedByLaser.indexOf(affectedBody);
if (fixtureIndex==-1) {
affectedByLaser.push(affectedBody);
entryPoint.push(point);
} else {
var rayCenter:b2Vec2=new b2Vec2((point.x+entryPoint[fixtureIndex].x)/2,(point.y+entryPoint[fixtureIndex].y)/2);
var rayAngle:Number=Math.atan2(entryPoint[fixtureIndex].y-point.y,entryPoint[fixtureIndex].x-point.x);
var polyVertices:Vector.=affectedPolygon.GetVertices();
var newPolyVertices1:Vector.=new Vector.();
var newPolyVertices2:Vector.=new Vector.();
var currentPoly:int=0;
var cutPlaced1:Boolean=false;
var cutPlaced2:Boolean=false;
for (var i:int=0; i0&&cutAngle<=Math.PI) {
if (currentPoly==2) {
cutPlaced1=true;
newPolyVertices1.push(point);
newPolyVertices1.push(entryPoint[fixtureIndex]);
}
newPolyVertices1.push(worldPoint);
currentPoly=1;
} else {
if (currentPoly==1) {
cutPlaced2=true;
newPolyVertices2.push(entryPoint[fixtureIndex]);
newPolyVertices2.push(point);
}
newPolyVertices2.push(worldPoint);
currentPoly=2;
}
}
if (! cutPlaced1) {
newPolyVertices1.push(point);
newPolyVertices1.push(entryPoint[fixtureIndex]);
}
if (! cutPlaced2) {
newPolyVertices2.push(entryPoint[fixtureIndex]);
newPolyVertices2.push(point);
}
createSlice(newPolyVertices1,newPolyVertices1.length);
createSlice(newPolyVertices2,newPolyVertices2.length);
if (affectedBody.GetUserData()!=null) {
debris.removeChild(affectedBody.GetUserData());
}
world.DestroyBody(affectedBody);
}
return 1;
}
private function findCentroid(vs:Vector., count:uint):b2Vec2 {
var c:b2Vec2 = new b2Vec2();
var area:Number=0.0;
var p1X:Number=0.0;
var p1Y:Number=0.0;
var inv3:Number=1.0/3.0;
for (var i:int = 0; i < count; ++i) {
var p2:b2Vec2=vs[i];
var p3:b2Vec2=i+1,numVertices:int):void {
var centre:b2Vec2=findCentroid(vertices,vertices.length);
var sliceBody:b2BodyDef=new b2BodyDef ;
sliceBody.position.Set(centre.x,centre.y);
sliceBody.type=b2Body.b2_dynamicBody;
sliceBody.userData=new Sprite ;
debris.addChild(sliceBody.userData);
sliceBody.userData.graphics.lineStyle(2,Math.random()*0xFFFFFF);
sliceBody.userData.graphics.beginFill(Math.random()*0xFFFFFF);
for (var i:int=0; i
Line 12: debris
is the Sprite which will act as container for all runtime generated debris.
Lines 21-22: add debris
to Display List, before canvas
Sprite, which will contain the laser ray, so the laser will always stay on top of debris.
Now that we have a container for all debris, let's modify createSlice
function to add some children to debris
:
Line 197: creates a custom Sprite for the sliced body, and saves it into body's userData
Line 198: adds the Sprite as a child of debris
Sprite
Line 199-200: set a random line and fill colors
Lines 203-207: at this time we are inside the for
loop which scans for all vertices. In order to draw the polygon, we have to move the graphic pen to the first vertex (when i
is equal to 0
) and then draw a line connecting it to the second vertex, then to the third vertex and so on.
Also note we are drawing **after** centre
has been subtracted from vertices, in order to have coordinates relative to polygon's centroid.
Lines 209-210: once we connected the last vertex with the previous one, it's time to connect the last vertex with the first one, to close the polygon, and call endFill
method to stop drawing.
This way we'll have our polygons drawn with their origins at (0,0) and, above all, static. They won't move. Now it's time to make polygons move and rotate according to the slice they represent. To do it, we need to modify updateWorld
function.
Lines 102-108: this is the good old snippet of code which comes in Box2D's Hello World example, allowing us to sync Flash Display Objects with Box2D objects.
Now we know how to draw and move custom Sprites, we just need to remove them once the object has been sliced. Lines 162-164 do this work.
And this task has been completed. During next step, the last one, we'll see how to use these custom sprites as masks allowing us to cut textured objects.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.