Breaking objects with Box2D – the realistic way

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.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

214 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers