Tutorial: Exploring the AS3 Bitmap Class Lesson 3 – Scale from the center with a Matrix.

Tutorial: Exploring the AS3 Bitmap Class Lesson 3 – Scale from the center with a Matrix.

In this third lesson we will explore a method of scaling the object from Lesson 1 and Lesson 2 using a Matrix operation. Like rotation, we could simple apply the scaleX and scaleY properties of the Bitmap object, but that would result in the scale operation starting from the top-left corner rather than the center. We could also place the Bitmap inside a Sprite object, and move it to -.5* width for x and -.5*height for y and the scale the Sprite. The focus of these lessons though is working with the raw Bitmap object to eliminate the over-head of using the Sprite class as the Bitmap container. So, we will scale using a Matrix operation. After creating a new Matrix for this scale operation we will add in the dynamic tile sheet animation and rotation Matrix back into the code to demonstrate all of them working in conjunction.

We will be using the dynamic tile sheet we created in Lesson 1, so if you have not familiarized yourself with that lesson and find yourself getting lost it would be a good idea to go back and take a look. You might also want to examine the Matrix rotation operations we looked at in Lesson 2 as we will not cover that subject in detail here.

Lesson 3 will comprise parts 7,8, and 9 of this series.

Part 7: Scaling a Bitmap with a Matrix

Scaling a Bitmap object with a Matrix is very similar to rotating it with a Matrix. To ensure that we are scaling around the center point of the object we will need to “translate” it to -.5*width for x and -.5*height for y, then scale it, then move it back to is original position on the screen.

We will be using two new class variables for part 7.

[cc lang=”javascript” width=”550″]
private var bitmapScaleMatrix:Matrix;
private var bitmapScale:Number = 1;
[/cc]

The bitmapScaleMatrix is a Matrix class instance that will be used to apply our Matrix scale operations to the BitmapToDisplay object. The bitmapScale variable will hold the current scale of the BitmapToDisplay. This will be updated in each frame tick. It is important to note that we need this variable because we cannot use the scaleX and scaleY attributes of the BitmapToDisplay. This single variable replaces them both in our example. If you want to have separate x and y scales then you simply need a second variable.

Inside our runGame() function we will scale the object on each frame tick. We will start with a scale of 1 and increase it by .1 until it reaches 2 and then drop it back down to .1 to start over again. Similar to the rotation in Lesson 2, we need to call the identity() function on the bitmapScaleMatrix before on each frame tick to reset it before we apply the new scale.

[cc lang=”javascript” width=”550″]bitmapScaleMatrix.identity(); //resets the matrix

bitmapScaleMatrix.translate(-16,-16);
bitmapScaleMatrix.scale(bitmapScale, bitmapScale);
bitmapScaleMatrix.translate(startLocation.x+16, startLocation.y+16);
bitmapToDisplay.transform.matrix = bitmapScaleMatrix;
bitmapScale += .1;
if (bitmapScale > 2) {
bitmapScale = .1;
}

[/cc]

Let’s step through this code:

1. We reset the Matrix with the identity() call.
2. We translate the so that the center of our 32×32 Bitmap is at the 0,0 origin. We do this with by translating to -16,-16
3. We call the scale() function of the Matrix and pass in the bitmapScale class variable for both the x scale and y scale.
4. We translate back to the original screen position for the Bitmap object plus the 16 by 16 pixels we used in the translation.
5. We apply the Matrix by setting the Bitmap.transform.matrix to = the bitmapScaleMatrix
6. We bump up of bitmapScale by .1 and check to make sure it is not larger than the maximum (2). If it is, we set it back to .1.

Here is the complete code for the “Part7” class. The new lines of code in part 7 are indicated and to remove the rotation and tile sheet animation we have commented out some lines. You don’t need to add those in now, but we will be adding them in in parts 8 and 9.

[cc lang=”javascript” width=”550″]
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.Transform

/**
* …
* @author Jeff Fulton
*/

public class Part7 extends Sprite {
private var bitmapToDisplay:Bitmap;

private var bitmapDataToDisplay:BitmapData;

private var bitmapScrollRect:Rectangle;
//private var animationDelay:int = 5;
//private var animationCount:int = 0;

//private var bitmapRotation:int = 0;
//private var bitmapRotationMatrix:Matrix;
private var startLocation:Point = new Point(84, 84);

//*** new in part 7
private var bitmapScaleMatrix:Matrix;
private var bitmapScale:Number = 1;

public function Part7():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}

private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
bitmapDataToDisplay = new BitmapData(64, 32, true, 0x00000000);
bitmapToDisplay = new Bitmap(bitmapDataToDisplay);
bitmapToDisplay.x = startLocation.x;
bitmapToDisplay.y = startLocation.y;
addChild(bitmapToDisplay);
createBitmapData();
//*** new in part 7
bitmapScaleMatrix= new Matrix();
//bitmapRotationMatrix= new Matrix();
bitmapScrollRect=new Rectangle(0, 0, 32, 32);
bitmapToDisplay.scrollRect = bitmapScrollRect;
bitmapToDisplay.smoothing = true; //makes it look better when rotates
addEventListener(Event.ENTER_FRAME, runGame,false,0,true);
}

private function createBitmapData():void {
//draw vertical line with setPixel32
for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 27, 0xff0066ff);
}
//copy ship to next tile
bitmapDataToDisplay.copyPixels(bitmapDataToDisplay,_
new Rectangle(0, 0, 32, 32), new Point(32, 0));
//add red thruster under the ship for this frame
for (ctr= 29; ctr <= 31; ctr++) {

bitmapDataToDisplay.setPixel32(47, ctr, 0xffff0000);
bitmapDataToDisplay.setPixel32(48, ctr, 0xffff0000);
}
}

private function runGame(e:Event):void {
bitmapScaleMatrix.identity(); //resets the matrix
bitmapScaleMatrix.translate(-16,-16);
bitmapScaleMatrix.scale(bitmapScale, bitmapScale);
bitmapScaleMatrix.translate(startLocation.x+16, startLocation.y+16);
bitmapToDisplay.transform.matrix = bitmapScaleMatrix;
bitmapScale += .1;
if (bitmapScale > 2) {
bitmapScale = .1;
}
//not needed in part 7

/*
animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}
//rotate with a matrix
bitmapRotation += 1;
if (bitmapRotation > 359){
bitmapRotation = 0;
}
var angleInRadians:Number = Math.PI * 2 * (bitmapRotation / 360);

bitmapRotationMatrix.identity(); //resets the matrix

bitmapRotationMatrix.translate(-16,-16);
bitmapRotationMatrix.rotate(angleInRadians);
bitmapRotationMatrix.translate(startLocation.x+16, startLocation.y+16);

bitmapToDisplay.transform.matrix = bitmapRotationMatrix;

trace(bitmapToDisplay.x + “,” + bitmapToDisplay.y + “,” + bitmapRotation );
*/
//end not needed in part 7
}
}
}

[/cc]

Notice the long line under the //copy ship to next tile comment. This line has been broken with an “_” and should be reattached if you are going to cut and paste the code.

Here is the working swf file:

/images/blog/bitmaplessons/part7.swf

As you can see, the ship stays in its position and the scale starts from the center of the object. It loops though scale sizes .1 to 2 in increments of .1.

Part 8: Adding Dynamic Tilesheet Animation back into the mix

For this section we will simply be adding the animation with the scrollRect of the bitmapToDisplay back into the code. It is currently commented our in the runGame() function from part 7.

[cc lang=”javascript” width=”550″]
animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}
}

[/cc]

For a refresher, here is the explanation from Lesson 1 part 3.

When then animationCount == animationDelay we will change the x value of the bitmapScrollRect. The value is either 0 (the start x of the first tile) or 32 (the start x of the second tile). It just jumps back and forth between these two values every 5 frames. This isn’t the most sophisticated animation technique, but it works fine for this example.

Notice that if there is a change to the bitmapScrollRect, we have to re-apply it to the bitmapToDisplay.scrollRect. The scrollRect property of a display object cannot be changed by reference, it must be re-applied as needed.

All of the above code will be inside a simple runGame function that we will create. This function is called on each frame by a simple EnterFrame event.

Simpily uncomment those lines from the Part7.as class file and you will have the animation back in to the code. Here is the what the runGame function should now look like.
You can save this new changed version as Part8.as. Make sure to change the class name to Part8 and constructor name to Part8 in the code it you choose to do this.

[cc lang=”javascript” width=”550″]
private function runGame(e:Event):void {
bitmapScaleMatrix.identity(); //resets the matrix
bitmapScaleMatrix.translate(-16,-16);
bitmapScaleMatrix.scale(bitmapScale, bitmapScale);
bitmapScaleMatrix.translate(startLocation.x+16, startLocation.y+16);
bitmapToDisplay.transform.matrix = bitmapScaleMatrix;
bitmapScale += .1;

if (bitmapScale > 2) {
bitmapScale = .1;
}

animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}

//not needed in part 8
/*
//rotate with a matrix
bitmapRotation += 1;
if (bitmapRotation > 359){
bitmapRotation = 0;
}

var angleInRadians:Number = Math.PI * 2 * (bitmapRotation / 360);

bitmapRotationMatrix.identity(); //resets the matrix

bitmapRotationMatrix.translate(-16,-16);
bitmapRotationMatrix.rotate(angleInRadians);
bitmapRotationMatrix.translate(startLocation.x+16, startLocation.y+16);

bitmapToDisplay.transform.matrix = bitmapRotationMatrix;

trace(bitmapToDisplay.x + “,” + bitmapToDisplay.y + “,” + bitmapRotation );
*/
//end not needed in part8
}

[/cc]

Here is the working swf file:

/images/blog/bitmaplessons/part8.swf

As you can see in the above example, we can simulate animation by changing the x value of the scrollRect property of a display object and at the same time we can scale the Bitmap object using the Matrix operations.

Part 9: Combining Scale and Rotation into a single Matrix

We covered the rotation portion in detail in the Lesson 2. To combine both rotation and scale into a single operation we will be creating a new single Matrix for both and will be removing the original two matrices we created: bitmapRotationMatrix and bitmapScaleMatrix. We will call the new Matrix the bitmapScaleRotationMatrix. In the variable definition section of the Part9.as file we will create this class level variable:

[cc lang=”javascript” width=”550″]
//*** new in part 9
private var bitmapScaleRotationMatrix:Matrix;
[/cc]

Since we only need a single Matrix for both we will have a single initial assignment in our init() function.

[cc lang=”javascript” width=”550″]
//*** new in part 9
bitmapScaleRotationMatrix= new Matrix();

[/cc]

Our runGame function will be completely new. It will combine the rotation and scale matrix operations with the existing dynamic tile sheet animation we uncommented in part 8.

[cc lang=”javascript” width=”550″]
private function runGame(e:Event):void {
animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}

//rotate with a matrix
bitmapRotation += 1;
if (bitmapRotation > 359){
bitmapRotation = 0;
}

var angleInRadians:Number = Math.PI * 2 * (bitmapRotation / 360);

bitmapScaleRotationMatrix.identity(); //resets the matrix

bitmapScaleRotationMatrix.translate(-16,-16);
bitmapScaleRotationMatrix.rotate(angleInRadians);
bitmapScaleRotationMatrix.scale(bitmapScale, bitmapScale);
bitmapScaleRotationMatrix.translate(startLocation.x+16, startLocation.y+16);

bitmapToDisplay.transform.matrix = bitmapScaleRotationMatrix;

trace(bitmapToDisplay.x + “,” + bitmapToDisplay.y + “,” + bitmapRotation );

bitmapScale += .1;
if (bitmapScale > 2) {
bitmapScale = .1;
}
}

[/cc]

Here are the highlights of this combined rotation and scale code:

1. First, ignore the animation section as it was covered in Lesson 1
2. Take a look at Lesson 2 for detailed information on the rotation Matrix operation..
3. We start by calling he identity() method of the bitmapScaleRotationMatrix to reset it on each frame tick.
4. We do the now familiar translate operation once for both scale and rotate.
5. We rotate.
6. We scale. (these can be done in any order)
7. We translate back to the original position.
8. We the apply bitmapScaleRotationMatrix to our bitmapToDisplay

Here is the complete code for part 9.

[cc lang=”javascript” width=”550″]
package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.Transform

/**
* …
* @author Jeff Fulton
*/
public class Part9 extends Sprite {

private var bitmapToDisplay:Bitmap;
private var bitmapDataToDisplay:BitmapData;

private var bitmapScrollRect:Rectangle;
private var animationDelay:int = 5;
private var animationCount:int = 0;

private var bitmapRotation:int = 0;
private var startLocation:Point = new Point(84, 84);

//*** new in part 9
private var bitmapScaleRotationMatrix:Matrix;
private var bitmapScale:Number = 1;

public function Part9():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}

private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
bitmapDataToDisplay = new BitmapData(64, 32, true, 0x00000000);

bitmapToDisplay = new Bitmap(bitmapDataToDisplay);
bitmapToDisplay.x = startLocation.x;
bitmapToDisplay.y = startLocation.y;
addChild(bitmapToDisplay);

createBitmapData();

//*** new in part 9
bitmapScaleRotationMatrix= new Matrix();

bitmapScrollRect=new Rectangle(0, 0, 32, 32);
bitmapToDisplay.scrollRect = bitmapScrollRect;

bitmapToDisplay.smoothing = true; //makes it look better when rotates

addEventListener(Event.ENTER_FRAME, runGame,false,0,true);
}

private function createBitmapData():void {
//draw vertical line with setPixel32
for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 27, 0xff0066ff);
}

//copy ship to next tile
bitmapDataToDisplay.copyPixels(bitmapDataToDisplay, _
new Rectangle(0, 0, 32, 32), new Point(32, 0));

//add red thruster under the ship for this frame
for (ctr= 29; ctr <= 31; ctr++) {
bitmapDataToDisplay.setPixel32(47, ctr, 0xffff0000);
bitmapDataToDisplay.setPixel32(48, ctr, 0xffff0000);

}
}

private function runGame(e:Event):void {

animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}

//rotate with a matrix
bitmapRotation += 1;
if (bitmapRotation > 359){
bitmapRotation = 0;
}

var angleInRadians:Number = Math.PI * 2 * (bitmapRotation / 360);

bitmapScaleRotationMatrix.identity(); //resets the matrix

bitmapScaleRotationMatrix.translate(-16,-16);
bitmapScaleRotationMatrix.rotate(angleInRadians);
bitmapScaleRotationMatrix.scale(bitmapScale, bitmapScale);
bitmapScaleRotationMatrix.translate(startLocation.x+16, _
startLocation.y+16);

bitmapToDisplay.transform.matrix = bitmapScaleRotationMatrix;

trace(bitmapToDisplay.x + “,” + bitmapToDisplay.y + “,” + bitmapRotation );

bitmapScale += .1;
if (bitmapScale > 2) {
bitmapScale = .1;
}
}
}
}

[/cc]

Notice the long lines like the one under the //copy ship to next tile comment. This line has been broken with an “_” and should be reattached if you are going to cut and paste the code.

Here is the working swf file:

/images/blog/bitmaplessons/part9.swf

In Lesson 4 we will start to create a BlitBitmap class that we can use standalone, or with in the Essential Flash Games Book framework.

Leave a Reply