8bitrocket.com
12Apr/100

Tutorial: Exploring the AS3 Bitmap Class Lesson 1 – Creating a basic dynamic tile sheet.

Tutorial: Exploring the AS3 Bitmap Class Lesson 1 - Creating a basic dynamic tile sheet.

We talk a lot about Blitting and BitmapData on this site, but we rarely take a step back and look at the container class for BitmapData, the Bitmap. In this series we will explore using the Bitmap class for many different applications and manipulate the BitmapData attribute to create animation and transformations. The end result of all the lessons will be a new class that we will add to the Essential Flash Games framework. With the AS3 Bitmap class we have a display object that we can manipulate to display BitmapData. This allows us to make use of the built-in features of a Flash Display object, but also the power of manipulating individual pixels of data for speedy rendering.

Lesson 1 will consist of parts 1-3 of the Bitmap class exploration. First we will take a look at dynamically drawing a simple shape on a Bitmap canvas. Next we will create a dynamic tile sheet using the BitmapData attribute of the Bitmap, and lastly we will create a simple animation using the dynamic tile sheet. Let's get started.

Part 1: Drawing pixels on a Bitmap canvas

The Bitmap class contains a BitmapData attribute. This provides the look of the Bitmap and allows for us to take control of the content of the Bitmap programmatically. The first thing we are going to do is draw a very simple "space ship" as a 32x32 image. The ship will look like this:

ship1

The "ship" we will create is a very simple upside down "T". This is a 32x32 png file with a transparent background. The ship does not touch the edges of the 32x32 size, but rather there is a 4 pixel buffer around the entire image. I created this one for reference only as it is a simple shape directly onto the bitmapData attribute inside our bitmap.

We will create a bitmap for our part1 code called: private var bitmapToDisplay:Bitmap;

We will also create a BitmapData object to hold the look of the ship: private var bitmapDataToDisplay:BitmapData;

We will draw the pixels of our ship by setting the color of the individual pixels of the bitmapDataToDisplay. Since we have some straight lines (2 pixels thick) that are symmetrical, we can create a very simple loop to draw our ship. We will use the setPixel32 function of the BitmapData:

[cc lang="javascript" width="550"]
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)
}

[/cc]

In this loop we create a vertical line from 4y to 27y at both pixel 15x and 16x (for a double thickness) and we create a horizontal line from 4x to 27x at pixels 26y and 27y. This mimics the png of the ship created above. Obviously this is probably the most simple space ship ever drawn, but it demonstrates how to manipulate pixels "on-the-fly" and will be the basis for much of the further discussion. Feel free to change up the look to meet your specific aesthetic desires (if it helps you make it through these tutorials, more power to ya!).

Here is the complete code for the "Part1" class.

[cc lang="javascript" width="550"]
package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;

/**
* ...
* @author Jeff Fulton
*/
public class Part1 extends Sprite {
private var bitmapToDisplay:Bitmap;
private var bitmapDataToDisplay:BitmapData;

public function Part1():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(32, 32, true, 0x00000000);
bitmapToDisplay = new Bitmap(bitmapDataToDisplay);
addChild(bitmapToDisplay);
createBitmapData();
}

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)
}
}
}

}

[/cc]

Here is the working swf file:

Part 2: Drawing a second frame on the same BitmapData to create a dynamic tile sheet

In part 1 we we created a single BitmapData instance called bitmapDataToDisplay and drew our simple space ship on it. In part 2 we will extend the size of this BitmapData from the original width of 32 pixels to a new width of 64 pixels. We will then draw the second frame of our simple space ship in what we will consider tile 2 of our tile sheet. Here is the code we will change in the init() function to make the width of the BitmapData 64 rather than 32:

bitmapDataToDisplay = new BitmapData(64, 32, true, 0x00000000);

Here is the ship image we will draw starting in pixel 32x and finishing in pixel 63x.

ship2

Again, the .png file is just for reference as we are going to be drawing into the BitmapData directly for this lesson. We can imbed the png in the IDE's library or at compile-time if we want to, but for this lesson we'll just use the dynamically drawn ship. The only difference between the ship1 and ship2 images is the small red "exhaust" at bottom of the "ship". This is the second frame of animation that would conceivably be used to simulate the ship thrusting through virtual space.

To draw this second frame into the BitmapData we will offset the original ship drawing by 32. The easy way to accomplish this is to copy the pixels from the first frame and drop them onto the second frame. We will use the BitmapData.copyPixels function to do this:

[cc lang="javascript" width="550"]
//copy ship to next tile
bitmapDataToDisplay.copyPixels(bitmapDataToDisplay, new Rectangle(0, 0, 32, 32), new Point(33, 0));
[/cc]

We pass a reference to the source for the copy (itself), a Rectangle representing the area to copy and a Point representing the upper left-hand corner to start copying the pixels. This draws a copy of the exact original ship pixels to what we will consider "frame 2" of our dynamically created tile sheet.

Next we will draw the red exhaust:

[cc lang="javascript" width="550"]
//add red thruster under the ship for this frame
for (ctr= 29; ctr <= 31; ctr++) {
bitmapDataToDisplay.setPixel32(48, ctr, 0xffff0000);
bitmapDataToDisplay.setPixel32(49, ctr, 0xffff0000);
}

[/cc]

Similar to how we created the

double blue lines for the original ship image, we create a 2 pixel width red line under the ship for the exhaust.

Here is the complete code for Part 2. It is very similar to Part 1 with some minor changes to accommodate the size of the BitmapData (extended to 64 pixel width) and the new drawing code.

[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.Point;
import flash.geom.Rectangle;

/**
* ...
* @author Jeff Fulton
*/

public class Part2 extends Sprite {

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

public function Part2():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);
addChild(bitmapToDisplay);

createBitmapData();
}

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(33, 0));

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

}

[/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:

As you can see, the entire BitmapData is displayed. While this is a good way to display an entire bitmap for an example, it doesn't help us animate the the ship. Let's take a look at how to do a classic "flip-book" animation of the ship next. We will do this by manipulating the Bitmap.scrollRect property.

Part 3: Animating the dynamic tile sheet with a scrollRect

The Bitmap.scrollRect property is used to define the viewable portion of the Bitmap object. It acts a little like a "mask" in that the rectangle will show through and everything else will be blocked out.

We will need to create a class level variable to hold the current Rectangle bounds that we want to display on our Bitmap. We will create a new variable to hold this called:

private var bitmapScrollRect:Rectangle;

In our init() function we will set the size of this to be 32x32:

bitmapScrollRect=new Rectangle(0, 0, 32, 32);

We will also apply it to the scrollRect property of the bitmapToDisplay.

bitmapToDisplay.scrollRect = bitmapScrollRect;

This is only good for the first frame as this will need to be re-applied each time we need to show a different part of the dynamic tile sheet. It does not work like most references where if we change the bitmapScrollRect's x value the bitmapToDisplay.scrollRect is also updated. We need to re-apply the assignment each time we change the x location of the bitmapScrollRect Rectangle.

We will update the bitmapScrollRect's x and y properties to change the location on the dynamic tile sheet we created. This will simulate animating the "thruster" at the bottom of our little space ship. We will create and EnterFrame event and use a counter for this animation. We will create two class level variables for the animation frame counting:

private var animationDelay:int = 5;

private var animationCount:int = 0;

The animationCount will be increased by one on each frame tick. When the count reaches the animationDelay the bitmapScrollRect will be updated. Here is the code that will accomplish this:

[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]

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.

Here is the complete code for Part 3. It is very similar to Part 2 with addition of the new animation variables and the runGame function for controlling the animation.

[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.Point;
import flash.geom.Rectangle;

/**
* ...
* @author Jeff Fulton
*/
public class Part3 extends Sprite {

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

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

public function Part3():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);
addChild(bitmapToDisplay);

createBitmapData();

bitmapScrollRect=new Rectangle(0, 0, 32, 32);
bitmapToDisplay.scrollRect = bitmapScrollRect;
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;
}
}
}

}

[/cc]

Again, 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:

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. This acts like a "mask" and leaves the rest of the dynamically created tile sheet hidden.

In the next lesson we will cover some rotation transformations of the Bitmap object using a Matrix.

If you enjoyed this post, please consider leaving a comment or subscribing to the RSS feed to have future articles delivered to your feed reader.
Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

No trackbacks yet.

This site is protected by Comment SPAM Wiper.