Talking about Actionscript 3, Box2D, Flash and Game development.
After seeing the character creation in Box2D Flash game creation tutorial – part 1, it’s time to add some coins to collect.
This process will involve some interesting Box2D features, like sensors and custom collision management.
I would suggest to read the basics of sensors at Erase Box: the tutorial and custom collision management at Creation of a Flash Stabilize! clone using Box2D – part 4.
Although they are both referred to an older Box2D version, they’ll introduce you to sensor and collisions.
Now the concept is simple: we are placing some circular sensors around the stage (the coins), then we’ll create a custom contact listener class to check whether the player is over a coin or not. If it’s over, we’ll remove the coin.
So this is the main script:
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import Box2D.Dynamics.*;
import Box2D.Collision.*;
import Box2D.Collision.Shapes.*;
import Box2D.Common.Math.*;
public class ball02 extends Sprite {
// world creation
public var world:b2World=new b2World(new b2Vec2(0,10.0),true);
public var world_scale:int=30;
// the player
public var player:b2Body;
// force to apply to the player
public var force:b2Vec2;
// variables to store whether the keys are pressed or not
// true = pressed;
// false = unpressed
public var left,right,up,down:Boolean=false;
// declaring my custom contact listener class
public var contact_listener=new custom_contact_listener();
public function ball02():void {
// assigning the contact listener to the world
world.SetContactListener(contact_listener);
// calling debug draw function
debug_draw();
// drawing the boundaries
draw_box(250,400,500,10,false,"ground");
draw_box(0,200,10,400,false,"left");
draw_box(500,200,10,400,false,"right");
draw_box(250,0,500,10,false,"roof");
// adding the player at 250,200
add_player(250,200);
// adding some coins
for (var i:int = 1; i<=5; i++) {
draw_coin(Math.random()*400+50,Math.random()*300+50,Math.random()*3+2);
}
// listeners needed for the game to work
addEventListener(Event.ENTER_FRAME, update);
stage.addEventListener(KeyboardEvent.KEY_DOWN,on_key_down);
stage.addEventListener(KeyboardEvent.KEY_UP,on_key_up);
}
// according to the key pressed, set the proper variable to "true"
public function on_key_down(e:KeyboardEvent):void {
switch (e.keyCode) {
case 37 :
left=true;
break;
case 38 :
up=true;
break;
case 39 :
right=true;
break;
case 40 :
down=true;
break;
}
}
// according to the key released, set the proper variable to "false"
public function on_key_up(e:KeyboardEvent):void {
switch (e.keyCode) {
case 37 :
left=false;
break;
case 38 :
up=false;
break;
case 39 :
right=false;
break;
case 40 :
down=false;
break;
}
}
// function to draw a coin
public function draw_coin(px,py,r):void {
var my_body:b2BodyDef= new b2BodyDef();
my_body.position.Set(px/world_scale, py/world_scale);
var my_circle:b2CircleShape=new b2CircleShape(r/world_scale);
var my_fixture:b2FixtureDef = new b2FixtureDef();
my_fixture.shape=my_circle;
// look! it's a sensor!!
my_fixture.isSensor=true;
var world_body:b2Body=world.CreateBody(my_body);
world_body.CreateFixture(my_fixture);
}
// simple function to draw a box
public function draw_box(px,py,w,h,d,ud):void {
var my_body:b2BodyDef= new b2BodyDef();
my_body.position.Set(px/world_scale, py/world_scale);
if (d) {
my_body.type=b2Body.b2_dynamicBody;
}
var my_box:b2PolygonShape = new b2PolygonShape();
my_box.SetAsBox(w/2/world_scale, h/2/world_scale);
var my_fixture:b2FixtureDef = new b2FixtureDef();
my_fixture.shape=my_box;
var world_body:b2Body=world.CreateBody(my_body);
world_body.SetUserData(ud);
world_body.CreateFixture(my_fixture);
}
// function to add the player
public function add_player(px,py):void {
var my_body:b2BodyDef= new b2BodyDef();
my_body.position.Set(px/world_scale, py/world_scale);
my_body.type=b2Body.b2_dynamicBody;
var my_circle:b2CircleShape=new b2CircleShape(10/world_scale);
var my_fixture:b2FixtureDef = new b2FixtureDef();
my_fixture.shape=my_circle;
player=world.CreateBody(my_body);
player.CreateFixture(my_fixture);
}
// debug draw
public function debug_draw():void {
var debug_draw:b2DebugDraw = new b2DebugDraw();
var debug_sprite:Sprite = new Sprite();
addChild(debug_sprite);
debug_draw.SetSprite(debug_sprite);
debug_draw.SetDrawScale(world_scale);
debug_draw.SetFlags(b2DebugDraw.e_shapeBit);
world.SetDebugDraw(debug_draw);
}
// function to be executed at every frame
public function update(e:Event):void {
// setting the force to null
force=new b2Vec2(0,0);
// according to the key(s) pressed, add the proper vector force
if (left) {
force.Add(new b2Vec2(-10,0));
}
if (right) {
force.Add(new b2Vec2(10,0));
}
if (up) {
force.Add(new b2Vec2(0,-20));
}
if (down) {
force.Add(new b2Vec2(0,5));
}
// if there is any force, then apply it
if (force.x||force.y) {
player.ApplyForce(force,player.GetWorldCenter());
}
world.Step(1/30,10,10);
world.ClearForces();
// scanning through all bodies
for (var worldbody:b2Body = world.GetBodyList(); worldbody; worldbody = worldbody.GetNext()) {
// if a body is marked as "remove"...
if (worldbody.GetUserData()=="remove") {
// ... just remove it!!
world.DestroyBody(worldbody);
}
}
world.DrawDebugData();
}
}
}
Let's see the interesting lines:
Line 22: declaring my contact_listener
variable, as custom_contact_listener
type
Line 25: assigning my custom contact listener class to Box2D world
Lines 36-38: calling five times the draw_coin
function passing three parameters: x position, y position and radius.
Lines 79-89: the draw_coin function... just a basic function that draw a circle... just notice at line 86 how I am declaring the circle as a sensor. This way the circle exists in the world but won't physically collide with anything. Also, a sensor should be a static body, or it will fall down outside the stage as it won't collide with anything.
Lines 150-156: scanning through all bodies to find, and eventually remove, bodies marked with remove
. Such marker is handled by the custom contact listener class, located in the custom_contact_listener.as
file:
package {
import Box2D.Dynamics.*;
import Box2D.Collision.*;
import Box2D.Collision.Shapes.*;
import Box2D.Dynamics.Joints.*;
import Box2D.Dynamics.Contacts.*;
import Box2D.Common.*;
import Box2D.Common.Math.*;
class custom_contact_listener extends b2ContactListener {
override public function BeginContact(contact:b2Contact):void {
// getting the fixtures that collided
var fixtureA:b2Fixture=contact.GetFixtureA();
var fixtureB:b2Fixture=contact.GetFixtureB();
// if the fixture is a sensor, mark the parent body to be removed
if (fixtureB.IsSensor()) {
fixtureB.GetBody().SetUserData("remove");
}
if (fixtureA.IsSensor()) {
fixtureA.GetBody().SetUserData("remove");
}
}
}
}
BeginContact
function will give us the fixtures involved in the collision. Then at lines 15-17 and 18-20 I am checking if the fixture is a sensor, then eventually mark its parent body to be removed.
Important: don't try to remove the body inside this function, because bodies have a locked
status while they are in the middle of a timestep, so you should remove a body only after you performed the Step
(line 147 of the main file). This gave me a little headache, so you've been warned!
This is the result:
Pick up all little circles moving the player tapping on arrow keys.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.