Creation of a Flash Stabilize! clone using Box2D – part 5
Welcome to the 5th part.
Today we’ll add some gameplay… in part 4 we managed collisions, now it’s time to add a one-minute timer (yes, this is a concept for the 60 seconds to fame contest).
I think, having colored stuff on the stage, making a “match 3 to remove” game would be too obvious… so the concept is this one: make a crate collide (collide! not stay in touch!) with three other crates of the same color to make it disappear.
The one I am showing you right now is just a prototype, but I am about to finish the complete game and it can lead to interesting gameplay variations, where bouncing is preferred to stacking.
Then, a simple timer made using the concepts seen at Understanding AS3 Timer Class and Drawing arcs with AS3 is placed in the center of the stage.
This is the main file:
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.utils.Timer;
import flash.events.TimerEvent;
import Box2D.Dynamics.*;
import Box2D.Collision.*;
import Box2D.Collision.Shapes.*;
import Box2D.Common.Math.*;
//
public class stabilize extends Sprite {
public var m_sprite: Sprite = new Sprite();
public var m_world:b2World;
// the_crate is the crate sprite
public var the_crate:crate=new crate();
// next_crate
public var next_crate:crate=new crate();
// droppable_area is the sprite representing the area your mouse
// must overlap to drop a crate
public var droppable_area:drop_area = new drop_area();
// this is the weight of the current crate I am dropping
public var cur_drop:int=Math.ceil(Math.random()*9);
// this is the color of the current crate I am dropping
public var cur_color:int=Math.ceil(Math.random()*5);
// weight and color of "next" crate
public var next_drop:int=Math.ceil(Math.random()*9);
public var next_color:int=Math.ceil(Math.random()*5);
// this is my custom contact listener class
public var contact_listener=new custom_contact_listener();
// the progressive number just keep track of dropped crates and
// assign an unique number to each of them
public var progressive:int=1;
// timer with a tick at every second
var time_count:Timer=new Timer(1000);
// I am going to represent the time on my_cancas sprite
var my_canvas:Sprite = new Sprite();
// degrees to radians conversion
var deg_to_rad=0.0174532925;
//
public function stabilize() {
var gravity:b2Vec2=new b2Vec2(0,9.8);
var worldAABB:b2AABB = new b2AABB();
worldAABB.lowerBound.Set(-1000,-1000);
worldAABB.upperBound.Set(1000,1000);
m_world=new b2World(worldAABB,gravity,true);
// adding the contact listener
m_world.SetContactListener(contact_listener);
m_sprite = new Sprite();
addChild(my_canvas);
addChild(m_sprite);
addChild(droppable_area);
addChild(the_crate);
// assigning the crate current weight and color
the_crate.weight.text=cur_drop.toString();
the_crate.gotoAndStop(cur_color);
addChild(next_crate);
// assigning "next" crate weight and color
next_crate.weight.text=next_drop.toString();
next_crate.gotoAndStop(next_color);
next_crate.x=250;
next_crate.y=385;
debug_draw();
AddBox(250/30,350/30,10/30,10/30,0,false);
AddBox(250/30,350/30,200/30,10/30,20,false);
// starting the timer
time_count.start();
// adding timer listener
time_count.addEventListener(TimerEvent.TIMER, show_time);
addEventListener(Event.ENTER_FRAME,Update);
stage.addEventListener(MouseEvent.MOUSE_DOWN,mousePressed);
stage.addEventListener(MouseEvent.MOUSE_MOVE,mouseMoved);
}
// showing the time pie
function show_time(event:TimerEvent) {
my_canvas.graphics.clear();
my_canvas.graphics.lineStyle(5,0xffffff,1);
draw_arc(my_canvas,250,200,50,0,360,1);
my_canvas.graphics.lineStyle(5,0x000000,1);
draw_arc(my_canvas,250,200,50,270,270+event.target.currentCount*6,1);
trace(event.target.currentCount);
}
public function mousePressed(e:MouseEvent) {
// look at hitTestPoint... the final true value says it's going to to check against the actual pixels
// instead of the bounding box
if (droppable_area.hitTestPoint(mouseX,mouseY,true)&&! the_crate.hitTestObject(m_sprite)) {
// notice the cur_drop parameter, I am passing the weight of the current crate to the AddBox function
AddBox(mouseX/30,mouseY/30,0.5,0.5,cur_drop,true);
}
}
public function mouseMoved(e:MouseEvent) {
the_crate.x=mouseX;
the_crate.y=mouseY;
if (droppable_area.hitTestPoint(mouseX,mouseY,true)) {
the_crate.alpha=1;
} else {
the_crate.alpha=0.5;
}
}
// function AddBox
// px: x position
// py: y position
// _halfwidth: half of the box width
// _halfheight: half of the box height
// density: density of the box (0: static)
// is_crate: if true, it's a crate (and you should render the proper movieclip
public function AddBox(px:Number,py:Number,_halfwidth:Number,_halfheight:Number,density:Number,is_crate:Boolean) {
var bodyDef:b2BodyDef = new b2BodyDef();
bodyDef.position.Set(px,py);
var boxDef:b2PolygonDef = new b2PolygonDef();
boxDef.SetAsBox(_halfwidth,_halfheight);
boxDef.density=density;
boxDef.friction=0.3;
boxDef.restitution=0.2;
if (is_crate) {
bodyDef.userData = new crate();
}
var body:b2Body=m_world.CreateBody(bodyDef);
body.CreateShape(boxDef);
body.SetMassFromShapes();
if (is_crate) {
addChild(bodyDef.userData);
// assigning current weight and color to the Box2D sprite bound to the box I created
bodyDef.userData.weight.text=cur_drop.toString();
// collz is the text that will count the number of collisions
bodyDef.userData.collz.text=0;
// assigning the progressive number to the crate...
bodyDef.userData.prog=progressive;
// ... and incrementing it for next one
progressive++;
bodyDef.userData.gotoAndStop(cur_color);
// updating current weight and color with the next ones
cur_drop=next_drop;
cur_color=next_color;
// changing current crate movieclip according to its weight and color
the_crate.weight.text=cur_drop.toString();
the_crate.gotoAndStop(cur_color);
// calculating next crate weight and color
next_drop=Math.ceil(Math.random()*9);
next_color=Math.ceil(Math.random()*5);
// updating next crate movieclip according to its weight and color
next_crate.weight.text=next_drop.toString();
next_crate.gotoAndStop(next_color);
}
}
public function Update(e:Event) {
var crate_num:int;
m_world.Step(1/30,10);
for (var bb:b2Body = m_world.m_bodyList; bb; bb = bb.m_next) {
if (bb.m_userData is Sprite) {
bb.m_userData.x=bb.GetPosition().x*30;
bb.m_userData.y=bb.GetPosition().y*30;
bb.m_userData.rotation = bb.GetAngle() * (180/Math.PI);
// checking for the number of collisions.
// if it's bigger than 2...
if (bb.m_userData.collz.text>2) {
// removing the crae movieclip
removeChild(bb.m_userData);
// removing the Box2D object from the world
bb.m_userData=null;
m_world.DestroyBody(bb);
}
}
}
}
public function debug_draw() {
var dbgDraw:b2DebugDraw = new b2DebugDraw();
var dbgSprite:Sprite = new Sprite();
m_sprite.addChild(dbgSprite);
dbgDraw.m_sprite=m_sprite;
dbgDraw.m_drawScale=30;
dbgDraw.m_alpha=1;
dbgDraw.m_fillAlpha=0.5;
dbgDraw.m_lineThickness=1;
dbgDraw.m_drawFlags=b2DebugDraw.e_shapeBit;
m_world.SetDebugDraw(dbgDraw);
}
public function draw_arc(movieclip,center_x,center_y,radius,angle_from,angle_to,precision) {
var angle_diff=angle_to-angle_from;
var steps=Math.round(angle_diff*precision);
var angle=angle_from;
var px=center_x+radius*Math.cos(angle*deg_to_rad);
var py=center_y+radius*Math.sin(angle*deg_to_rad);
movieclip.graphics.moveTo(px,py);
for (var i:int=1; i<=steps; i++) {
angle=angle_from+angle_diff/steps*i;
movieclip.graphics.lineTo(center_x+radius*Math.cos(angle*deg_to_rad),center_y+radius*Math.sin(angle*deg_to_rad));
}
}
}
}
And this is the collision manager. Refer to part 4 to see how can you use it.
package {
//
import Box2D.Collision.*;
import Box2D.Collision.Shapes.*;
import Box2D.Dynamics.Contacts.*;
import Box2D.Dynamics.*;
import Box2D.Common.Math.*;
import Box2D.Common.*;
//
public class custom_contact_listener extends b2ContactListener {
// this array will hold all collisions among crates
var collz:Array=new Array ;
//
public override function Add(point:b2ContactPoint):void {
// if the bodies that collided have an userData different from null,
// then we can say they are crates.
// So, if collided bodies are crates...
if (point.shape1.GetBody().GetUserData()!=null&&point.shape2.GetBody().GetUserData()!=null) {
// if both crates have the same color...
if (point.shape1.GetBody().GetUserData().currentFrame==point.shape2.GetBody().GetUserData().currentFrame) {
// getting progressive number of both crates
var num1:int=point.shape1.GetBody().GetUserData().prog;
var num2:int=point.shape2.GetBody().GetUserData().prog;
// if the collision array of the num1-th crate does not exists...
if (collz[num1]==undefined) {
// create it
collz[num1]=new Array ;
// populate it with the num2-th crate
collz[num1].push(num2);
} else {
// if already exists, check if the num2-th crate already exists...
if (collz[num1].indexOf(num2)==-1) {
// if not, add it just like before
collz[num1].push(num2);
}
}
// same thing for the collision array of the num2-th crate
if (collz[num2]==undefined) {
collz[num2]=new Array ;
collz[num2].push(num1);
} else {
if (collz[num2].indexOf(num1)==-1) {
collz[num2].push(num1);
}
}
// updating the number of collision shown in both crates
point.shape1.GetBody().GetUserData().collz.text=collz[num1].length;
point.shape2.GetBody().GetUserData().collz.text=collz[num2].length;
}
}
}
//
public override function Persist(point:b2ContactPoint):void {
}
//
public override function Remove(point:b2ContactPoint):void {
}
//
public override function Result(point:b2ContactResult):void {
}
}
}
And this is the result:
Make crates collide until the collision counter reaches 3 to make them disappear. No need to download, just cut/paste these scripts on the ones you can find at part 4.
If you make something interesting out of it, let me know and I'll publish it.