Slicing, splitting and cutting objects with Box2D – part 3: cutting your own Sprites

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.

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

215 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
// Stairs
// 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