Talking about The Moops - Combos of Joy game, Actionscript 3, Box2D, Flash and Game development.
Here we are with the 2nd part of the tutorial to create a game like The Moops. The guys at Heavy Boat, the studio behind the game, are following the tutorial too, so I hope I’ll try to make something really similar to their game.
In the first part I showed you how to fire the ball and hit squares, now it’s time to remove hit squares after a certain amount of time. There’s nothing new as we only have to work on contact listeners as explained in this post.
Let’s see the main class:
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.Shapes.*;
import Box2D.Common.Math.*;
public class moops extends Sprite {
private var world:b2World=new b2World(new b2Vec2(0,0),true);
private var worldScale:int=30;
private var timeCount:Timer=new Timer(500);
private var contactListener=new customContactListener();
public function moops():void {
world.SetContactListener(contactListener);
timeCount.start();
debugDraw();
addEventListener(Event.ENTER_FRAME, update);
stage.addEventListener(MouseEvent.CLICK,onClick);
timeCount.addEventListener(TimerEvent.TIMER, onTime);
}
private function onClick(e:MouseEvent):void {
var angle:Number=Math.atan2(mouseX,mouseY-480)-Math.PI/2;
addball(20*Math.cos(angle),-20*Math.sin(angle));
}
private function onTime(event:TimerEvent):void {
addBox(300+Math.random()*200,-100,40);
}
private function addball(xVel:Number,yVel:Number):void {
var ball:b2BodyDef= new b2BodyDef();
ball.userData=new Object();
ball.userData.name="ball";
ball.type=b2Body.b2_dynamicBody;
ball.position.Set(20/worldScale, 460/worldScale);
var circle:b2CircleShape=new b2CircleShape(15/worldScale);
var ballFixture:b2FixtureDef = new b2FixtureDef();
ballFixture.shape=circle;
ballFixture.friction=0;
ballFixture.density=1;
ballFixture.restitution=1;
var ballBody:b2Body=world.CreateBody(ball);
ballBody.CreateFixture(ballFixture);
ballBody.SetLinearVelocity(new b2Vec2(xVel,yVel));
}
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 addBox(xOrigin:Number,yOrigin:Number,size:Number):void {
var box:b2BodyDef= new b2BodyDef();
box.userData=new Object();
box.userData.name="box";
box.userData.stamina=50;
box.userData.hit=false;
box.position.Set(xOrigin/worldScale,yOrigin/worldScale);
box.type=b2Body.b2_dynamicBody;
var square:b2PolygonShape = new b2PolygonShape();
square.SetAsBox(size/2/worldScale, size/2/worldScale);
var boxFixture:b2FixtureDef = new b2FixtureDef();
boxFixture.shape=square;
boxFixture.friction=0;
boxFixture.density=4;
boxFixture.restitution=1;
var boxBody:b2Body=world.CreateBody(box);
boxBody.CreateFixture(boxFixture);
boxBody.SetLinearVelocity(new b2Vec2(0,5));
}
private function update(e:Event):void {
world.Step(1/30,10,10);
world.ClearForces();
world.DrawDebugData();
for (var currentBody:b2Body = world.GetBodyList(); currentBody; currentBody=currentBody.GetNext()) {
if (currentBody.GetPosition().y*worldScale>600 || currentBody.GetPosition().y*worldScale<(100*-1)) {
world.DestroyBody(currentBody);
}
if (currentBody.GetUserData()!=null&¤tBody.GetUserData().name=="ball") {
var velocity:b2Vec2=currentBody.GetLinearVelocity();
if (velocity.Length()!=20) {
var speedOffset:Number=20/velocity.Length();
currentBody.SetLinearVelocity(new b2Vec2(velocity.x*speedOffset,velocity.y*speedOffset));
}
}
if (currentBody.GetUserData()!=null&¤tBody.GetUserData().hit) {
currentBody.GetUserData().stamina--;
if (currentBody.GetUserData().stamina<=0) {
world.DestroyBody(currentBody);
}
}
}
}
}
}
Let's examine lines 14 and 16:
private var contactListener=new customContactListener();
First I need to create a new custom contact listener, then I have to bind it to the world to use it in the simulation.
world.SetContactListener(contactListener);
At this time we need to add some custom properties to boxes we are creating:
private function addBox(xOrigin:Number,yOrigin:Number,size:Number):void {
var box:b2BodyDef= new b2BodyDef();
box.userData=new Object();
box.userData.name="box";
box.userData.stamina=50;
box.userData.hit=false;
box.position.Set(xOrigin/worldScale,yOrigin/worldScale);
box.type=b2Body.b2_dynamicBody;
var square:b2PolygonShape = new b2PolygonShape();
square.SetAsBox(size/2/worldScale, size/2/worldScale);
var boxFixture:b2FixtureDef = new b2FixtureDef();
boxFixture.shape=square;
boxFixture.friction=0;
boxFixture.density=4;
boxFixture.restitution=1;
var boxBody:b2Body=world.CreateBody(box);
boxBody.CreateFixture(boxFixture);
boxBody.SetLinearVelocity(new b2Vec2(0,5));
}
From lines 58 to 61 I'm attaching an Object to the box, whose properties are the name, the stamina and a Boolean variable called hit
.
Now things will work this way: if a box is hit by the ball or by another box, that is if the box collides with any other body in the stage, hit
is set to true
and if hit
is true
its stamina will be decreased at every frame. Once the stamina reaches zero, the box is removed.
This routine will be placed in update function:
private function update(e:Event):void {
world.Step(1/30,10,10);
world.ClearForces();
world.DrawDebugData();
for (var currentBody:b2Body = world.GetBodyList(); currentBody; currentBody=currentBody.GetNext()) {
if (currentBody.GetPosition().y*worldScale>600 || currentBody.GetPosition().y*worldScale<(100*-1)) {
world.DestroyBody(currentBody);
}
if (currentBody.GetUserData()!=null&¤tBody.GetUserData().name=="ball") {
var velocity:b2Vec2=currentBody.GetLinearVelocity();
if (velocity.Length()!=20) {
var speedOffset:Number=20/velocity.Length();
currentBody.SetLinearVelocity(new b2Vec2(velocity.x*speedOffset,velocity.y*speedOffset));
}
}
if (currentBody.GetUserData()!=null&¤tBody.GetUserData().hit) {
currentBody.GetUserData().stamina--;
if (currentBody.GetUserData().stamina<=0) {
world.DestroyBody(currentBody);
}
}
}
}
Lines 90-95 deal with stamina.
And that's enough for main class, now we just have to extend the built in b2ContactListener
in a file called
customContactListener.as
.
package {
import Box2D.Dynamics.*;
import Box2D.Dynamics.Contacts.*;
class customContactListener extends b2ContactListener {
override public function BeginContact(contact:b2Contact):void {
var fixtureA:b2Fixture=contact.GetFixtureA();
var fixtureB:b2Fixture=contact.GetFixtureB();
if(fixtureA.GetBody().GetUserData().name=="box"){
fixtureA.GetBody().GetUserData().hit=true;
}
if(fixtureB.GetBody().GetUserData().name=="box"){
fixtureB.GetBody().GetUserData().hit=true;
}
}
}
}
The new class just set hit
property to true
if a box gets hit.
This is the result:
Mouse click to shoot the ball, try to hit a box and make the box hit more boxes, and see what happens.
Download the source code. Next time, we'll add some graphics.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.