Advertisement

Introduction to QuickBox2D: Part 2

by

This is the second part in a three part tutorial series about QuickBox2D. It's recommended that you read each part of this series in order. So if you haven't read part one, you should do so now. You can check it out here.

In this tutorial we will take a look at some of the more intermediate level features of QuickBox2D. We'll see how to create polygon rigid bodies. We'll learn how to fine tune our simulation using additional param values such as restitution, linearDamping and angularDamping. We'll discuss some powerful Box2D methods that are exposed by QuickBox2D and we'll talk about FRIM (framerate independent motion).

Reminder: This should go without saying, but don't forget to put the Box2D and QuickBox2D libraries either
next to your .fla file or in your classpath.

This tutorial will walk you through the creation of a medium sized simulation designed to touch on specific aspects of QuickBox2D and Box2D. Check out the demo and relieve a little stress..


Step 1: Setting Up Your Script

Open up Flash and paste the following code into the actions for frame one:

[SWF(width = 800, height=600, frameRate=60, backgroundColor=0x000000)];

import com.actionsnippet.qbox.*;

var sim:QuickBox2D = new QuickBox2D(this);

/**-----------------------------
  -- insert variable definitions below this comment
  ------------------------------*/

// all the code to build rigid bodies 
// is contained within this function
buildSimulation();

sim.start();
sim.mouseDrag();

function buildSimulation():void {
	
	// set some default colors and create stage walls
	sim.setDefault({fillColor:0x113366, fillAlpha:0.5, lineColor:0x3355AA});
	sim.createStageWalls();
	
/**-----------------------------
   -- insert ridgid body instantiation code below this comment
   ------------------------------*/

}

Once you have the above code in your timeline go ahead and test your movie to make sure everything is set up properly. Not much will happen when you run this code, just make sure that you don't have any errors before moving on.

Most of what you see in the above code should be familiar to you. There are however a few important things to note. As we add additional code to this file we'll need to define a few variables. For organizational purposes you'll want to add those variables underneath the comment that reads:

 /**-----------------------------
  -- insert variable definitions below this comment
  ------------------------------*/

For simplicity, we've also created a function called buildSimulation() to wrap all of our rigid body instantiation. This function is called before the start() and mouseDrag() methods of QuickBox2D. It contains some default color settings for our rigid bodies as well as a call to createStageWalls(). Most of the code we look at in this tutorial will need to be pasted directly under the comment that reads:

 /**-----------------------------
   -- insert ridgid body instantiation code below this comment
   ------------------------------*/

Step 2: Polygons and the Points Param

I'm assuming that you know the difference between convex, concave and complex polygons. If you don't know or don't remember the difference, you may want to have a glance at the wikipedia article on the subject.

QuickBox2D allows you to define polygons in two ways. The first way uses automatic polygon triangulation, which allows you to simply define the x and y coordinates for the contour of a polygon. There is no limitation on the number of points in your contour. Paste the following code into your buildSimulation() function and test your movie:

// triangle
sim.addPoly({x:3, y:17, points:[0,0,  1,1,  0,1, 0,0]});
	   
// concave poly
sim.addPoly({x:7, y:17, points:[1,0,  2,2,  1,1.2,  0, 2]});

The above code defines two polys. The first line defines a simple right triangle that gets positioned at (3,17) and the next line defines a concave polygon that gets positioned at (7,17). We make use of a param called points which is an array of x and y coordinates. One dimensional arrays of xy coordinates can be tricky, the below graphic should clear up any confusion you
might have about the code we just added:

When creating points arrays, you need to be careful not to have a contour that overlaps itself, doing so will confuse the triangulation script and cause an error.

You'll notice that the second polygon is made up of two triangles. This is QuickBox2D's triangulation code at work, it basically breaks your contour up into a bunch of triangles. If you don't want to see each triangle, you use can the wireframe param. Try altering your concave poly code as follows:

// concave poly
sim.addPoly({x:7, y:17, wireframe:false, points:[1,0,  2,2,  1,1.2,  0, 2]});

If you need to create more complex polygons you should probably build yourself some kind of simple polygon editor. There is an example of such an editor on actionsnippet.com. You may want to take a look at it for a few minutes before reading on. It should help solidify your understanding of the way QuickBox2D triangulates polygons.


Step 3: Polygons and the Verts Param

There is another way to create polygons using QuickBox2D which is a bit trickier. If you're very concerned with the performance of your simulation you might consider using this method over the previously discussed method. You may also want to use this method if you need to create a simple convex poly such as a trapazoid. Add the following code into your buildSimulation() function:

sim.addPoly({x:13, y:17, verts:[[-1,-1,  1,-1,  2, 1,  -2, 1]]});

This code draws a trapazoid. Test your movie and have a look.

There are a few differenes between the verts param and the points param. An obvious difference is that the verts array is a two dimensional array. Each nested array contains a list of xy coordinates, this array can be used to define up to eight vertices. You cannot define a concave or complex polygon by using a single nested array. Instead, you must define a series of convex polygons (again, each with a max of eight vertices). You should define each convex polygon in clockwise order, starting with the top most point and working back around. This can be tricky, but it's easier than it sounds. Add the following code into your buildSimulation() function and test your movie:

sim.addPoly({x:18, y:15, verts:[[0,-1, 1,1, -1,1],
								[1,1,  2,1.5,  1,2],
								[1,2,  0,3,   -1,3, -1,2],
							    [-1.5,2,  -1,2.5,   -1,3, -2,3, -2,2.5]]});

This code draws a few polygons together. The result is a small group of convex polys. If you plan on using this feature, you might want to take a break at this point and try to create something with the verts param. It's interesting to see just how sensitive this feature is, you'll get weird results if you don't abide by the restrictions mentioned in the previous paragraph.


Step 4: Restitution

Restitution is like the elasticity or bounciness of a rigid body. By default restitution is set somewhere between 0-1, but higher values are allowed. Let's take a look at what can be done with restitution, copy the following code into your buildSimulation() function and test your movie:

// perpetually bouncing ball
	sim.addCircle({x:3, y:13, radius:1, restitution:1});
	
	// set restutution to 1.2 to create a spring board
	sim.addBox({x:24, y:15, width:3, height:0.25,
				fillColor:0xDD0000, lineAlpha:0,
				angle:0.1, restitution:1.2, density:0});
				
	// normal circle
	sim.addCircle({x:24, y:10, radius:0.5});

The first thing we create with this code is a circle that bounces forever. By setting the restitution param to 1 it won't stop bouncing as long as it is left alone. The next thing we create is a red box that is slightly rotated, has a density of 0 (is static) and has a restitution of 1.2. The restitution of 1.2 makes it so that things bouncing off of it will gain some energy. To test this, we add a circle directly above it. If you watch this circle carefully you'll notice that each time it hits the red box it bounces a little bit higher, until finally it hits the ceiling and bounces off of the red box altogether.

Restitution is really the first step to making your rigid bodies act like they are made out of different materials. A restitution of 0 would be good for a rigid body that is supposed to be made of stone; a restitution of 0.8 would be good for something made of very bouncy rubber.


Step 5: Angular and Linear Damping

If you want to prevent a rigid body from translating or rotating too fast, you can dampen its linear or angular velocity. Add this code to your buildSimuation() function and test your movie.

	// green box
    sim.addBox({x:3, y:5, width:1, height:1, angularDamping:5, fillColor:0x22CC00});
    
    // red box
    sim.addBox({x:8, y:5, width:1, height:1, fillColor:0xFF0000});
    
    // small platform
	sim.addBox({x:6, y:8, width:10, height:0.25, density:0});

Here we add a green box with an angularDamping value of 5. This will cause the gren box to stop rotating very quickly when its thrown. For comparison we add a red box and left its angularDamping value at the default of 0. Since the simulation is beginning to get a little cluttered, we've added a small platform underneath these two boxes so it's easy to move them around and observe the difference between them.

Linear damping is just like angularDamping but for translation. Let's add angularDamping to the green box:

	// green box
    sim.addBox({x:3, y:5, width:1, height:1, angularDamping:5, linearDamping:5, fillColor:0x22CC00});

If you test your movie, you'll notice that the green box falls slower than the red box and if you throw it around a bit you'll see that it almost appears to float. Try playing around with the values a little bit before moving on.

I find I like to have angularDamping on anything that I want to rotate realistically when thrown. By default, when you throw a box around, you'll notice it will rotate around many many times. I find myself using linearDamping a bit less, but it's good if you don't want something to move around to fast. It's also good if you want to make something appear as though its being affected by air resistance, such as a deflated balloon or a piece of styrofoam.


It's important to note that setting the angularDamping or linearDamping params above 1 will them to become sensitive to the timeStep. This may not mean anything to you at the moment, but we will discuss it further later in this tutorial.


Step 6: Friction

In Box2D, friction generally ranges from 0-1. By default all QuickObjects have a friction of 0.5. If we decrease this value things start looking very slippery. Add the below code to your buildSimulation() function and test your movie:

    // gray box
    sim.addBox({x:3, y:9, width:1, height:1, friction:1, fillColor:0xCCCCCC});
	
    // light blue box
    sim.addBox({x:8, y:9, width:1, height:1, friction:0, fillColor:0xCCCCFF, fillAlpha:1});
	
	 // small platform

	sim.addBox({x:6, y:12, width:10, height:0.25, density:0});

In the above code we add a gray box with a friction of 1, a light blue box with a friction of 0 and another small platform to make the two boxes easy to observe. Try sliding each box along the platform. You'll notice that the gray box doesn't slide very much, while the light blue box seems very slippery, like an ice cube.

Messing around with friction always makes me think of ice levels from those old Nintendo sidescrollers...


Step 7: The isBullet Param (Continuous Collision Detection CCD)

Box2D has something called CCD. This is a more accurate, more cpu intensive form of collision detection. If you want to use this type of collision on a rigid body, you need to make use of the isBullet param. As the name suggests, you often use CCD for bullets and other fast moving rigid bodies. Copy the below code into your buildSimulation():

var boxes:Array = [];
	boxes[0] = sim.addBox({x:1, y:0, width:2, height:0.25});
	boxes[1] = sim.addBox({x:1, y:2, width:2, height:0.25});
	boxes[2] = sim.addBox({x:0, y:1, width:0.25, height:2.25});
	boxes[3] = sim.addBox({x:2, y:1, width:0.25, height:2.25});
	sim.addGroup({x:12, y:6, objects:boxes});
								
	// if isBullet is false, circles will fall out of box
	for (var i:int = 0; i<4; i++){
		sim.addCircle({x:13, y:7, radius:0.2, isBullet:true});
	}

Here we create a hollow box using a group object. We then put four small circles into the hollow box and set their isBullet property to true. This prevents the small circles from shooting out of the hollow box group object. Test this by throwing the hollow box around the simulation as fast as you can. You'll notice that all the circles remain within the walls of the hollow box. Now try setting the isBullet param to false and do the same thing, almost immediately you'll see the circles fly out all over the place.

It's worth noting that CCD is automatically used on static bodies.


Step 8: The FixedRotation Param

The fixedRotation param is very easy to understand. It's a boolean that if set to true will prevent a rigid body from rotating. This is useful for preventing the character in a game from falling over. Add this code to your buildSimulation() function and test your movie:

sim.addBox({x:19, y:3, width:1, height:2, fillColor:0xFFFFFF, fixedRotation:true});

This code creates a white box (it's slightly gray because of the default alpha value of 0.8) with fixeRotation set to true. If you try throwing it around you'll notice that it does not rotate. Pretty simple huh?

It's worth noting that you can still set the angle param. Give this a try:

sim.addBox({x:19, y:3, width:1, height:2, fillColor:0xFFFFFF, fixedRotation:true, angle:1});

Step 9: The isSleeping Param

Box2D rigid bodies fall asleep by default to conserve CPU. This act of falling asleep occurs when a rigid body has come to rest. Sometimes you want to prevent a rigid body from falling asleep using the allowSleep boolean (we'll see a good example of this later in the tutorial). Sometimes you want to start a rigid body off asleep. For these cases you use the isSleeping param. It may not be obvious, but isSleeping can be used to create breakable objects. Add this code to your buildSimulation() function and test your movie:

/*---------------------------------
	  -- Step 10: allowSleep
	 ---------------------------------*/
	// create 10 boxes in a grid formation
	for (i = 0; i<10; i++){
		sim.addBox({x:13 + (i % 2) * 0.5, 
					y:3 + int(i / 2) * 0.5, 
					width:0.5, height:0.5, 
					lineAlpha:0,
					fillColor:0x4444FF,
					isSleeping:true});
	}

This code creates 10 boxes in a grid formation, the boxes don't have a stroke and are colored with a saturated blue. The isSleeping param is set to true by default, causing the boxes to remain at their starting position until something else hits them and wakes them up. If you throw another rigid body at this grid of boxes you'll notice that we've created the illusion of a breakable rigid body.

If you're not familiar with the technique we're using to arrange the boxes in a grid, you can read this tutorial about arranging DisplayObject in grid formations over at learningActionScript3.com


Step 10: The groupIndex Param

The groupIndex param can be used to give you a finer control over the way collision is handled between rigid bodies. It is an integer that is either negative or positive. If two rigid bodies have the same negative groupIndex value they won't collide with one another, if they have the same positive groupIndex value they will collide. Add this code to your buildSimulation() function:

for (i = 0; i<4; i++){
	  sim.addBox({x:2 + i , y:2, fillColor:0xFFFF00,
				 width:0.5, height:1.5, groupIndex:-1});
	}
	
    // small platform
	sim.addBox({x:6, y:4, width:10, height:0.25, density:0});

This code creates four yellow rectangles with a groupIndex value of -1. It also creates another small platform to make observation of the groupIndex param easy. If you test your movie you'll notice that these rectangles will not collide with one another.

I find myself using this groupIndex quite a bit. It's essential when dealing with complex simulations. There are two other related params called maskBits and categoryBits. I have yet to need these very much, however you can read a bit more about them in the Box2D manual if you're interested.


Step 11: Getting Ready for Key Controls

As mentioned in part 1 of this tutorial. Every creation method (addBox(), addPoly() etc...) returns a QuickObject. When you want to start keeping track of elements in a simulation you'll want to store QuickObject references in variables. We're going to begin setting up a rigid body to be controlled with the arrow keys. In order to do this we need to do a little preparation. First, add this variable definition to your code (remember to put it under the variable definition comment outside of the buildSimulation() function).

/**-----------------------------
  -- insert variable definitions below this comment
  ------------------------------*/
var character:QuickObject;

Next add the following code to your buildSimulation() function:

var charPartA:QuickObject = sim.addBox({x:0, y:0, width:2, height:0.8});
var charPartB:QuickObject = sim.addBox({x:0, y:0, width:0.8, height:2});
character = sim.addGroup({objects:[charPartA, charPartB], x:8, y:3, angularDamping:0.8, linearDamping:0.8, allowSleep:false});

This creates a small group object built up of two boxes. We set linear and angular damping properties so it won't move too fast when we start controlling it with the keyboard. We also use a param called allowSleep to make sure that the character rigid body doesn't fall asleep (without this key controls won't work properly). Test your movie to make sure that the character group object has been added to the simulation properly.

WARNING: These next few steps get a little tricky, so pay close attention to each instruction. If you start to get worried that you can't follow all the changes to the code, after the tricky part is over you may consider downloading the complete fla to compare.


Step 12: Adding Basic Key Control Code

For certain types of key control just using KeyboardEvents alone won't cut it. Normally I will use something like this:

var key:Object = new Object();
stage.addEventListener(KeyboardEvent.KEY_DOWN,onKeyPressed);
stage.addEventListener(KeyboardEvent.KEY_UP, onKeyReleased);
function onKeyPressed(evt:KeyboardEvent):void {
	key[evt.keyCode] = true;
}
function onKeyReleased(evt:KeyboardEvent):void {
	key[evt.keyCode] = false;
}

This code creates an Object (associative array or a map) called key. When a key is pressed for the first time, a property is dynamically created on the key Object, the keyCode is used for the name of the property and since we're on a KEY_DOWN event the property is set to true. When a KEY_UP event occurs the newly created property is set to false. You should copy and paste this code all the way down at the bottom of your script at this point.

If you've never seen this technique before, it may seem a little odd to you. If that's the case, all you need to know is that this code prevents some minor issues with using KeyboardEvents alone and that if we want to respond to keyboard input, we can simply do something like this in a loop:

if (key[Keyboard.UP]){
  // make the character jump
}

Now, when using a loop in conjunction with QuickBox2D you can listen for Event.ENTER_FRAME if you like, but later versions of QuickBox2D have a special type of event called QuickBox2D.STEP. You should always use QuickBox2D.STEP in favor of Event.ENTER_FRAME, we'll talk a little more about this when we get to FRIM. Inside your buildSimulation() function you should add a listener for this event:

 sim.addEventListener(QuickBox2D.STEP, onStep);

We'll also need to add a listener function directly below our buildSimulation() function:

...
sim.addEventListener(QuickBox2D.STEP, onStep);
} //  end of our buildSimulation() function

// -- copy from this point on...
// our listener function
function onStep(evt:Event):void {
   // we'll add some key control stuff here soon
}

Ok, now we're almost ready to start doing some key controlled motion...


Step 13: Key Controls, Linear Velocity and the Body Property

All QuickObjects have a property called body. This property contains a reference to a Box2D b2Body instance. By exposing this b2Body instance, QuickBox2D gives you access to a bunch of Box2D methods. We're going to use the GetLinearVelocity() and SetLinearVelocity(). For a full list of all the b2Body methods, have a look here.

The linear velocity of a rigid body is defined by a 2D vector, the x and y values of this vector are added to the x and y position of a given rigid body. The linear velocity of a rigid body varies over time due to gravity, collisions with other objects, friction etc... Box2D has a class specifically designed for dealing with vectors called b2Vec2. The SetLinearVelocity() method takes a b2Vec2 as its first and only argument. We need to import b2Vec2, so add this line below the import statement for QuickBox2D:

// you already have this line
import com.actionsnippet.qbox.*

// add this line:
import Box2D.Common.Math.*;

b2Vec2 is contained within the Math package. Now let's take a look at SetLinearVelocity(). We need to add some code inside of the onStep() listener function:

// you should already have this function defintion
function onStep(evt:Event):void {
   // add this conditional
   if (key[Keyboard.RIGHT]){
      character.body.SetLinearVeloctiy(new b2Vec2(10, 0));
   }
}

Try testing your movie and hitting the right key. The character rigid body should float to the right of the screen. Let's add code for a left key, add the following code inside your onStep() function:

// add this: 
 if (key[Keyboard.LEFT]){
      character.body.SetLinearVeloctiy(new b2Vec2(-10, 0));
 }

You may want to try messing around with the x and y values of the b2Vec2 to get a feel for how it works.

This is pretty nice, but the motion is pretty robotic. There are a few things we can do to make these key controls work a bit nicer. The first thing we can do is just organizational. It's not very convenient or flexible to constantly create new b2Vec2 instances, so let's add a variable for working with the character's linear velocity. Add this variable definition to your code:

// you already have this
var character:QuickObject;

// add this line
var charVel:b2Vec2 = new b2Vec2();

If you don't pass anything to the b2Vec2 constructor the x and y properties default to 0. Next, alter your onStep() function to look like this:

function onStep(evt:Event):void {
    charVel = character.body.GetLinearVelocity();
    
    if (key[Keyboard.RIGHT]){
		charVel.x += 0.8;
		character.body.SetLinearVelocity(charVel); 
	}
	if (key[Keyboard.LEFT]){
		charVel.x -= 0.8;
		character.body.SetLinearVelocity(charVel); 
	}
	if (key[Keyboard.UP]){
		 charVel.y = -10;
		 character.body.SetLinearVelocity(charVel); 
	} 
	if (key[Keyboard.DOWN]){
		 charVel.y = 0.8;
		 character.body.SetLinearVelocity(charVel); 
	} 
}

If you test your move you'll notice that the key controls for the character have improved significantly.

At the beginning of the onStep() function we call the GetLInearVelocity() method. We do this so that we don't force the linear velocity to change abruptly by resetting all the values. Instead, we read the linear velocity and alter according to keyboard input. When we want to move right or left we just alter the x property slightly (by 0.8 meters). When we move up, we need a little more force because of gravity so we alter the y property of charVel by -10. When we move down, gravity is working in our favor so we can just use 0.8 meters again.

At this point we need to perform a little code clean up. Let's move the key control code into its own function:

function onStep(evt:Event):void {
    charVel = character.body.GetLinearVelocity();
    keyControls();
}

function keyControls():void{
   if (key[Keyboard.RIGHT]){
		charVel.x += 0.8;
		character.body.SetLinearVelocity(charVel); 
	}
	if (key[Keyboard.LEFT]){
		charVel.x -= 0.8;
		character.body.SetLinearVelocity(charVel); 
	}
	if (key[Keyboard.UP]){
		 charVel.y = -10;
		 character.body.SetLinearVelocity(charVel); 
	} 
	if (key[Keyboard.DOWN]){
		 charVel.y = 0.8;
		 character.body.SetLinearVelocity(charVel); 
	} 
}

If you are feeling organized you might wrap up the -10 and 0.8 values into variables. For the simplicity of this tutorial I'll leave that out, but feel free to do so if you like.

As mentioned before, there are a whole bunch of other methods in the b2Body class. You may want to take a few minutes and play around with some of the other ones. They're all pretty intuitive - some of them will be covered in part three of this tutorial series.


Step 14: QuickObject x and y Properties

It's quite common to need to force a rigid body to jump from one place to another. You can do this using the QuickObject x and y properties. Add the below teleportCharacter() function to your code and call it continuously in your onStep() function:

... 
teleportCharacter();

}// end of onStep() method

function teleportCharacter():void{
	// restrict character motion
	if (char.x < 4 && char.y > 12){
		char.x = 8;
		char.y = 3;
		// zero out the characters velocity
		charVel.SetZero();
		char.body.SetLinearVelocity(charVel);
	}
}

This code makes it so that when your character moves to the lower left hand corner of the screen, it teleports back to its original starting position of (8,3). Test your movie and work your way to the lower left corner of the screen to see it in action.

This works by continuously checking the x and y properties of our character rigid body and then resetting them if the given condition is met. We also call the SetZero() method the b2Vec2 charVel - as the name implies this sets x and y properties of charVel to 0, causing the character's linear velocity to be set to zero.


Step 15: Adding Two Platforms

In this next step we're going to use the x, y and angle properties of QuickObjects to create some simple floating platforms. The first thing we need to do is add some variable definitions:

// you already have these...
var character:QuickObject;
var charVel:b2Vec2 = new b2Vec2();

// add these 3 new vars
var oscillator:QuickObject;
var oscTheta:Number = 0;
var rotator:QuickObject;

First we'll populate the oscillator variable with a static rectangle body. Add this code to your buildSimulation() function:

 oscillator = sim.addBox({x:21, y:8, width:2.5, height:0.25, fillColor:0x009965, density:0});

This is pretty straight forward so far. We want to oscillate this platform up and down - for that we'll use Math.sin(). Add the below runOscillator() function to your code and call it continuously in your onStep() function. Test your movie.

... 
runOscillator();
} // end of onStep() function

function runOscillator():void{
	oscillator.y = 8 + 4 * Math.cos(oscTheta);
	oscTheta += 0.01;
}

This code oscillates the y property of the oscillator rigid body between 4 and 16 meters. If you're unfamiliar with sine and cosine, you may want to read up a bit on some basic trigonometry. The main point of this example is that we are able to use a simple equation to alter the y property of a rigid body.

Next we'll add our rotator platform. Copy this code into your buildSimulation() function:

rotator = sim.addBox({x:16, y:8, width:3, height:0.3, fillColor:0x009965, density:0});

Nothing special happening here, we've just positioned this platform so that it doesn't interfere with any other parts of our simulation. Now it needs to rotate - we'll add a function called runRotator() to do just that:

... 
runRotator();
} // end of onStep() function

function runRotator():void{
	rotator.angle += 0.02;
}

Go ahead and test your movie. Take a few moments to move rigid bodies on and off the platforms.

While testing you may have noticed a that rigid bodies can fall through the oscillating platform. If you didn't notice this, take any rigid body with the exception of the character and place it on the oscillating platform. You'll notice that after a little while it falls through the oscillating platform like this:

This occurs because the body sitting on the oscillating platform has fallen asleep and Box2D has stopped checking for collisions on it. The easy solution is to set the allowSleep param to false on any rigid body that might need to sit on the oscillating platform. This works nicely, but is not very efficient. A more elegant solution would be if we could tell if a collision were occurring on the oscillating platform and make sure that the thing it is colliding with doesn't fall asleep. This is possible using something called contact points. We cover contact points in part 3 of this tutorial, but if you're curious you can see a few examples of them here.


Step 16: The userData Property

In Box2D, rigid bodies have a property called userData. This is just an untyped variable intended for use with custom information about a given rigid body.

QuickBox2D populates the userData property with a DisplayObject. So, if you want to add a MouseEvent to the skin of a QuickObject, or if you want to draw some additional graphics into a QuickObject using the graphics property, you need to use the userData property. We'll see how to do both of these things in this step. Start off by defining a variable called shootBox with all your other variables:

// you already have these:
var oscTheta:Number = 0;
var rotator:QuickObject;

// add this
var shootBox:QuickObject;

Now we'll create shootBox and draw an X inside it. Add the following code to your buildSimulation() function and test your movie:

     shootBox = sim.addBox({x:24, y:17, width:1.5, height:1.5});
	
	 // draw an X into shootBox userData.graphics
	 shootBox.userData.graphics.lineStyle(0, 0xFFFFFF, 0.5);
	 shootBox.userData.graphics.moveTo(-20, -20);
	 shootBox.userData.graphics.lineTo(20, 20);
	 shootBox.userData.graphics.moveTo(20, -20);
	 shootBox.userData.graphics.lineTo(-20, 20);

This will work the same way if you're using custom skins.

Adding MouseEvents works exactly the same way. Let's create a simple listener function for use with a MouseEvent, add this function to your code:

function onShootBoxClick(evt:MouseEvent):void {
    var currLinearVec:b2Vec2 = shootBox.body.GetLinearVelocity();
	currLinearVec.x -= 15;
	currLinearVec.y -= 15;
	shootBox.body.SetLinearVelocity(currLinearVec);
}

We can use this listener function in conjunction with shootBox now, add this code to your buildSimulation() function:

shootBox.addEventListener(MouseEvent.CLICK, onShootBoxClick);

When you test your movie, if you click on the shootBox rigid body it's linear velocity is reset causing it to shoot up and to the left.


Step 17: Angular Velocity

We can add one final touch to our shootBox using angular velocity. Angular velocity is the velocity of a given rigid bodies rotation. To add a little spin to the shootBox, add this line into the onShootBoxClick() listener function:

shootBox.body.SetAngularVelocity(-10);

Go ahead and test your movie.

An angular velocity of -10 will cause the shootBox to rotate counter clockwise at 10 radians per second. A positive value would cause the shootBox to rotate clockwise.


Step 18: FRIM

FRIM (frame rate independent motion) is used to attempt to keep the speed of the Box2D simulation constant no matter what frame rate your swf is running at. This is good because frame rates will vary from browser to browser and from computer to computer.

Go to line one of your code and try changing the frame rate to 20, 120, 5 etc... each time testing the swf to see how it looks.

[SWF(width = 800, height=600, frameRate=20, backgroundColor=0x000000)];

What you'll notice as you change the frameRate value, is that the speed of the rigid bodies remains relatively constant. The difference is that a different number of frames is being used to represent the same animation. This is really important in games - if your game animated very slowly on an older computer it might become significantly easier to play.

We can toggle FRIM by altering the arguments that we pass to the QuickBox2D constructor. You should be up at the top of your script, go ahead and change the line where we instantiate QuickBox2D to look like this:

var sim:QuickBox2D = new QuickBox2D(this, {frim:false});

Test your movie a few times, each time changing the frameRate value and notice that lower frame rates now cause the simulation to run slower, while higher frame rates will speed up the simulation. When you're done testing set FRIM back to true and set your frameRate back to 60.

Note: The second argument of the QuickBox2D constructor is an object of params related to the entire simulation.

Earlier in the tutorial we used the QuickBox2D.STEP event instead of an enterFrame. This may be obvious, but QuickBox2D.STEP gets triggered independently of frameRate. By default QuickBox2D attempts to update the simulation at 60Hz (or 60 times per second), so if a swf is only running at 30fps the QuickbBox2D.STEP event will run approximately 2 timer per frame. You can change how often QuickBox2D.STEP occurs by altering the timeStep param like this:

var sim:QuickBox2D = new QuickBox2D(this, {frim:true, timeStep:1/30.0});

The above would lower the timeStep to 30hz, causing QuickBox2D.STEP to get called every other frame at 60fps or once per frame at 30fps. You can try this if you like but I've found that 60hz and a frameRate of 60fps works the best most of the time.


Step 19: Gravity

We can alter the gravity in the simulation by adding additional params to the QuickBox2D params argument. Try this:

var sim:QuickBox2D = new QuickBox2D(this, {frim:true, gravityY: -20});

When you test your movie you'll notice that gravity is now reversed. By default gravityY is 20, setting it to -20 has caused everything to fall up. You can also alter gravityX (this is good for creating wind effects):

var sim:QuickBox2D = new QuickBox2D(this, {frim:true, gravityY:-20, gravityX:10});

This is pretty funny looking:

But if you think about it for a moment, it could be very cool to have variable gravity and wind in a game. Try adding this snippet to your code, you can just add it right under the QuickBox2D instantiation code:

stage.addEventListener(MouseEvent.CLICK, onClick);
function onClick(evt:*){
	sim.gravity.x += 3;
}

Now when you click on the stage you slowly increase the x gravity. I recommend removing or commenting out these gravity alterations at this point.


What's Next?

That wraps up part two of this series of tutorials. The third and final part of this tutorial will cover some advanced techniques. We'll look at how QuickBox2D handles all of the different types of Box2D joints (there are lots of them). We'll also go over contact points and special types of collisions.

I hope you enjoyed this tutorial. If you create something interesting with what you've learned in this tutorial feel free to post a link to it in the comments :D

Advertisement