Talking about Actionscript 3, Box2D and Flash.
It all started with the Box2D slice engine, which evolved in the Box2D explosion engine.
Now it’s time to break objects in the realistic way using the same concept.
This is what I made:
Click on the stage to shoot a sphere in a random direction and watch how it breaks the wooden pole. I made it with “bullet time” so you can see how it works, and also because I sometime experience some problems when running it at full speed, but it’s just a prototype at the moment.
Anyway, it works this way:
* When the sphere touches the wooden pole, my custom contact listener detects it.
* At this time, I know sphere position and velocity. Assuming the sphere could cut the wooden pole, I can determine the raycast according to sphere position and direction
* I apply the raycast to the wooden pole, splitting it in two
* Finally I clone the sphere, creating another one with the same speed and direction of the old one, the one which hit the wooden pole, to recreate the impact on the recently added wooden pole slices.
And that’s it.
I am posting the fully commented source code:
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Matrix;
import flash.display.BitmapData;
import Box2D.Dynamics.*;
import Box2D.Collision.*;
import Box2D.Collision.Shapes.*;
import Box2D.Common.Math.*;
public class Main extends Sprite {
private var world:b2World=new b2World(new b2Vec2(0,10),true);
// You can see the reason for creating the enterPointsVec in the coments in the intersection() method.
private var enterPointsVec:Vector. = new Vector.();
private var numEnterPoints:int=0;
private var worldScale:Number=30;
// custom contact listener
private var customContact=new CustomContactListener();
public function Main() {
// defining the custom contact listener
world.SetContactListener(customContact);
// calling the debug draw. This is used to show you the bitmaps are correctly applied,
// and because I did not want to draw the walls :)
debugDraw();
// this is the BitmapData representation of the wood
// check the library to see both the raw image and the sprite
var woodBitmap:BitmapData=new BitmapData(50,400);
woodBitmap.draw(new WoodImage());
// adding the four static, undestroyable walls;
addWall(320,480,640,20);
addWall(320,0,640,20);
addWall(0,240,20,480);
addWall(640,240,20,480);
// createBody builds the final body and applies the bitmap.
createBody(400,270,new [new b2Vec2(-25,-200),new b2Vec2(25,-200),new b2Vec2(25,200),new b2Vec2(-25,200)],woodBitmap);
// You can see the reason for creating the enterPointsVec in the coments in the intersection() method.
enterPointsVec=new Vector.(numEnterPoints);
// listeners
stage.addEventListener(MouseEvent.MOUSE_DOWN, shoot);
addEventListener(Event.ENTER_FRAME, update);
}
// my old friend debugDraw function
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);
}
// function that will add the sphere and launch it
// arguments are respectively x position, y position, radius, x velocity and y velocity
private function addSphere(pX:Number,pY:Number,r:Number,vX:Number,vY:Number):void {
var theSphere:b2Body;
var sphereShape:b2CircleShape=new b2CircleShape(r/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.position.Set(pX/worldScale,pY/worldScale);
sphereBodyDef.bullet=true;
sphereBodyDef.userData={assetName:"sphere",collided:false};
theSphere=world.CreateBody(sphereBodyDef);
theSphere.CreateFixture(sphereFixture);
theSphere.SetLinearVelocity(new b2Vec2(vX,vY));
}
// simple function to add a static wall
private function addWall(pX:Number,pY:Number,w:Number,h:Number):void {
var wallShape:b2PolygonShape = new b2PolygonShape();
wallShape.SetAsBox(w/worldScale/2,h/worldScale/2);
var wallFixture:b2FixtureDef = new b2FixtureDef();
wallFixture.density=0;
wallFixture.friction=1;
wallFixture.restitution=0.5;
wallFixture.shape=wallShape;
var wallBodyDef:b2BodyDef = new b2BodyDef();
wallBodyDef.position.Set(pX/worldScale,pY/worldScale);
wallBodyDef.userData={assetName:"wall"};
var wall:b2Body=world.CreateBody(wallBodyDef);
wall.CreateFixture(wallFixture);
numEnterPoints++;
}
// function to create and texture a dynamic body
private function createBody(xPos:Number, yPos:Number, verticesArr:Vector., texture:BitmapData) {
// I need this temp vector to convert pixels coordinates to Box2D meters coordinates
var vec:Vector.=new Vector.();
for (var i:Number=0; i, count:uint):Number {
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=poly.GetVertices(),numVertices:int=poly.GetVertexCount();
var shape1Vertices:Vector. = new Vector.(), shape2Vertices:Vector. = new Vector.();
var origUserData:userData=sliceBody.GetUserData().textureData,origUserDataId:int=origUserData.id,d:Number;
var polyShape:b2PolygonShape=new b2PolygonShape();
var body:b2Body;
// First, I destroy the original body and remove its Sprite representation from the childlist.
world.DestroyBody(sliceBody);
removeChild(origUserData);
// The world.RayCast() method returns points in world coordinates, so I use the b2Body.GetLocalPoint() to convert them to local coordinates.;
A=sliceBody.GetLocalPoint(A);
B=sliceBody.GetLocalPoint(B);
// I use shape1Vertices and shape2Vertices to store the vertices of the two new shapes that are about to be created.
// Since both point A and B are vertices of the two new shapes, I add them to both vectors.
shape1Vertices.push(A, B);
shape2Vertices.push(A, B);
// I iterate over all vertices of the original body. ;
// I use the function det() ("det" stands for "determinant") to see on which side of AB each point is standing on. The parameters it needs are the coordinates of 3 points:
// - if it returns a value >0, then the three points are in clockwise order (the point is under AB)
// - if it returns a value =0, then the three points lie on the same line (the point is on AB)
// - if it returns a value <0, then the three points are in counter-clockwise order (the point is above AB).
for (var i:Number=0; i0) {
shape1Vertices.push(verticesVec[i]);
}
else {
shape2Vertices.push(verticesVec[i]);
}
}
// In order to be able to create the two new shapes, I need to have the vertices arranged in clockwise order.
// I call my custom method, arrangeClockwise(), which takes as a parameter a vector, representing the coordinates of the shape's vertices and returns a new vector, with the same points arranged clockwise.
shape1Vertices=arrangeClockwise(shape1Vertices);
shape2Vertices=arrangeClockwise(shape2Vertices);
// setting the properties of the two newly created shapes
var bodyDef:b2BodyDef = new b2BodyDef();
bodyDef.type=b2Body.b2_dynamicBody;
bodyDef.position=sliceBody.GetPosition();
var fixtureDef:b2FixtureDef = new b2FixtureDef();
fixtureDef.density=origFixture.GetDensity();
fixtureDef.friction=origFixture.GetFriction();
fixtureDef.restitution=origFixture.GetRestitution();
// creating the first shape, if big enough
if (getArea(shape1Vertices,shape1Vertices.length)>=0.05) {
polyShape.SetAsVector(shape1Vertices);
fixtureDef.shape=polyShape;
bodyDef.userData={assetName:"debris",textureData:new userData(origUserDataId,shape1Vertices,origUserData.texture)};
addChild(bodyDef.userData.textureData);
enterPointsVec[origUserDataId]=null;
body=world.CreateBody(bodyDef);
body.SetAngle(sliceBody.GetAngle());
body.CreateFixture(fixtureDef);
}
// creating the second shape, if big enough;
if (getArea(shape2Vertices,shape2Vertices.length)>=0.05) {
polyShape.SetAsVector(shape2Vertices);
fixtureDef.shape=polyShape;
bodyDef.userData={assetName:"debris",textureData:new userData(origUserDataId,shape2Vertices,origUserData.texture)};
addChild(bodyDef.userData.textureData);
enterPointsVec.push(null);
numEnterPoints++;
body=world.CreateBody(bodyDef);
body.SetAngle(sliceBody.GetAngle());
body.CreateFixture(fixtureDef);
}
}
private function arrangeClockwise(vec:Vector.):Vector. {
// The algorithm is simple:
// First, it arranges all given points in ascending order, according to their x-coordinate.
// Secondly, it takes the leftmost and rightmost points (lets call them C and D), and creates tempVec, where the points arranged in clockwise order will be stored.
// Then, it iterates over the vertices vector, and uses the det() method I talked about earlier. It starts putting the points above CD from the beginning of the vector, and the points below CD from the end of the vector.
// That was it!
var n:int=vec.length,d:Number,i1:int=1,i2:int=n-1;
var tempVec:Vector.=new Vector.(n),C:b2Vec2,D:b2Vec2;
vec.sort(comp1);
tempVec[0]=vec[0];
C=vec[0];
D=vec[n-1];
for (var i:Number=1; ib.x) {
return 1;
}
else if (a.x
and this is the custom contact listener:
package {
import Box2D.Dynamics.*;
import Box2D.Collision.*;
import Box2D.Dynamics.Contacts.*;
import Box2D.Common.Math.*;
class CustomContactListener extends b2ContactListener {
override public function PreSolve(contact:b2Contact, oldManifold:b2Manifold):void {
var nameA:String=contact.GetFixtureA().GetBody().GetUserData().assetName.toString();
var nameB:String=contact.GetFixtureB().GetBody().GetUserData().assetName.toString();
if ((nameA=="wood" && nameB=="sphere")||(nameA=="sphere" && nameB=="wood")) {
if (nameA=="sphere") {
contact.GetFixtureA().GetBody().GetUserData().collided=true
}
else {
contact.GetFixtureB().GetBody().GetUserData().collided=true
}
}
}
}
}
I hope this will give you fresh ideas for some original game design. Download the source code.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.