Requirements

Prerequisite knowledge

This article assumes a working knowledge of Flash Builder 4 and ActionScript 3 projects.

User level

Beginning

Note: This article was created based on Flash Builder 4 beta. Minor changes in the description and code may be necessary before it can be applied to Flash Builder 4.
For many types of games, the user experience depends on how many pixels you can have on the screen and how fast you can move them around. When animating large numbers of DisplayObject objects, such as MovieClip or Sprite objects, Adobe Flash Player may not perform well enough for effective game play. Flash Player must traverse the display object tree and compute the rendering output for each vector-based DisplayObject. This eats up CPU cycles and can be a real bottleneck, especially on lower-end machines.
For games with many on-screen animations that can be pre-rendered into a bitmap, a technique known as blitting can provide a solution. Blitting is not the answer to every performance issue but it does enable smooth, consistent animation frame rates across a wide range of machines. The term blitting comes from the BitBLT routine created for Xerox Alto computers. BitBLT, pronounced "bit blit," stands for bit-block (image) transfer, a technique that takes several bitmaps and combines them into one bitmap. In Flash Player it is faster to copy bitmap pixels into one rendered bitmap than to render each DisplayObject separately.
In this article, I describe the software blitting technique and provide sample code so you can apply it in ActionScript.
 

 
Introducing sprite sheets

A game is made up of graphical assets, for example a car on a racetrack or a tree in a forest. For this article these assets will be bitmaps. A group of bitmaps put together in a single image file is called a sprite sheet. For example, a sprite sheet might contain all the frames for an animation of a character walking. The term is derived from sprite, which, in the computer graphics world, is an image or animation integrated into a larger scene. Although the blitting technique can use various sources of bitmap data, this article will focus on sprite sheets.
 
What comprises a sprite sheet?
A sprite sheet can be a combination of all kinds of bitmaps of varying sizes. Assembling all the graphical assets in one (or a few) large image files reduces load time (it's faster to open and read one large file that contains 100 frames than it is to open and read 100 smaller files) and provides compression benefits. Typically sprite sheets will hold similarly sized bitmaps that form a sequence or animation around a particular game asset. For example, the sprite sheet used in this tutorial is made up of five columns and four rows of 40 x 40 pixel tiles, each containing a brown collector (see Figure 1).
 
Sample sprite sheet showing 20 tiles, each one sized at 40 x 40 pixels (browncollector.png)
Figure 1. Sample sprite sheet showing 20 tiles, each one sized at 40 x 40 pixels (browncollector.png)
 
 
Setting up the ActionScript project
Before you can try out the example code, you'll need to complete these steps to set up the project in Flash Builder 4:
  1. Download and unzip the sample files for this tutorial.
  2. Choose File > New > ActionScript Project to create the project.
  3. Type ActionScriptBlitting for the Project Name and click Finish.
  4. Copy the following files and folder from the sample files into the project's default package: ActionScriptBlittingPart1.as, ActionScriptBlittingPart2.as, ActionScriptBlittingPart3.as, ActionScriptBlittingPart4.as, and spritesheets. The spritesheets folder contains the PNG files used by the ActionScript examples.
  5. In the Package Explorer, right-click the ActionScriptBlitting project you just created and select Properties.
  6. In the Properties dialog box, click ActionScript Applications.
  7. Click Add, select ActionScriptBlittingPart1.as, and then click OK.
  8. Repeat Step 7 for ActionScriptBlittingPart2.as, ActionScriptBlittingPart3.as, and ActionScriptBlittingPart4.as.
  9. Click OK.
You now have all of the sample code in Flash Builder 4, and you'll be able to run all of the examples.
 
Embedding a sprite sheet with ActionScript
You can embed images in ActionScript through the use of the Embed metadata tag. (Read Embedding metadata with Flash for more information.) Once they're embedded, you can create an instance of the class and attach it to the display list, as in ActionScriptBlittingPart1.as.
ActionScriptBlittingPart1.as
 
package { import flash.display.Sprite; [SWF(width=480, height=320, frameRate=24, backgroundColor=0xE2E2E2)] public class ActionScriptBlittingPart1 extends Sprite { public function ActionScriptBlittingPart1() { addChild(new BrownCollector()); } [Embed(source="spritesheets/browncollector.png")] public var BrownCollector:Class; } }
To run the first example, follow these steps:
  1. In the Package Explorer, right-click ActionScriptBlittingPart1.as and select Run Application.
  2. When your browser opens, you should see the entire PNG image from browncollector.png with all the tiles (see Figure 1).
  3. Close the browser window.
 

 
Blitting a sprite sheet

As a second step, you'll use the Bitmap and BitmapData Flash Player APIs to copy one tile (or frame) from the sprite sheet onto the screen. This is done by using the BitmapData.copyPixels() method, which copies the pixels of the input bitmap data onto the bitmap instance that is making the call. Central to blitting in ActionScript, the copyPixels() method also provides parameters to define the input bitmap region to be copied as well as how to define and merge alpha pixels.
ActionScriptBlittingPart2.as
 
package { import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.Sprite; import flash.geom.Point; import flash.geom.Rectangle; [SWF(width=480, height=320, frameRate=24, backgroundColor=0xE2E2E2)] public class ActionScriptBlittingPart2 extends Sprite { public function ActionScriptBlittingPart2() { // Create input bitmap instance spritesheet = (new BrownCollector() as Bitmap).bitmapData; // Add a Bitmap to the display list that will copyPixels() to. canvas = new BitmapData(480, 320, true, 0xFFFFFF); addChild(new Bitmap(canvas)); rect = new Rectangle(0, 0, 40,40); // 1st Tile //** Section 1 ** // // rect = new Rectangle(40, 0, 40, 40); // 2nd Tile // rect = new Rectangle(80, 0, 40, 40); // 3rd Tile // ... // rect = new Rectangle(160, 120, 40, 40); // 20th Tile canvas.copyPixels(spritesheet, rect, new Point(0, 0)); //** END Section 1 **/ /** Section 2 ** // for (var i:int = 0; i < 20; i++) { rect.x = (i % 5) * 40; rect.y = int(i / 5) * 40; canvas.copyPixels(spritesheet, rect, new Point(i*10, 0)); // Section 3: // canvas.copyPixels(spritesheet, rect, // new Point(i*10, 0), null, null, true); } //** END Section 2 **/ } [Embed(source="spritesheets/browncollector.png")] public var BrownCollector:Class; public var canvas:BitmapData; public var spritesheet:BitmapData; public var rect:Rectangle; } }
To see this in action, run ActionScriptBlittingPart2.as by right-clicking it in the Package Explorer and selecting Run Application. You'll see the first tile from the sprite sheet displayed in your browser (see Figure 2).
 
ActionScriptBlittingPart2 output (with Section 1 code)
Figure 2. ActionScriptBlittingPart2 output (with Section 1 code)
 
 
Displaying all tiles
Now that you can draw one tile, why not draw all of the tiles? To draw them all, follow these steps:
  1. Open ActionScriptBlittingPart2.as in Flash Builder 4.
  2. Find the code labeled "Section 1" and comment it out.
  3. Find the code labeled "Section 2" and uncomment it.
  4. Save the file.
  5. Run ActionScriptBlittingPart2.as again.
The Section 2 code uses a for loop to draw each tile offset horizontally 10 pixels from the previous tile (see Figure 3).
 
ActionScriptBlittingPart2 output (with Section 2 code)
Figure 3. ActionScriptBlittingPart2 output (with Section 2 code)
 
That doesn't look quite right, and this is where the alpha parameters of BitmapData.copyPixels() come into play. The last three parameters (alphaBitmapData, alphaPoint, and mergeAlpha) provide different ways of handling alpha regions. Since the sprite sheet PNG already has alpha data inside the image, you don't need alphaBitMapData or alphaPoint. You simply need to turn on alpha merging by setting the last parameter, mergeAlpha, to true.
To make this change:
  1. Comment out the following call to copyPixels() in Section 2:
 
canvas.copyPixels(spritesheet, rect, new Point(i*10, 0));
  1. Uncomment the following call to copyPixels() in Section 3:
 
canvas.copyPixels(spritesheet, rect, new Point(i*10, 0), null, null, true);
  1. Save the file.
  2. Run ActionScriptBlittingPart2.as again. You should see a series of overlapping tile images (see Figure 4).
 
ActionScriptBlittingPart2 output (with Section 2 code)
Figure 4. ActionScriptBlittingPart2 output (with Section 3 code)
 

 
Animating from a sprite sheet

Now that you know how to display the bitmap data from the sprite sheet, you're ready to animate it. The code in ActionScriptBlittingPart3.as animates the brown collector image and moves it to the location of any mouse click on the Stage.
 
A smooth, consistent timer in Flash Player
The basic idea is to use a timer that can be based on Flash Player frames, a timer, or a combination of the two. A typical approach is to use a combination of the ENTER_FRAME event and a call to getTimer() to control the speed of the animations across various computer environments. The code below is an excerpt from the ActionScript class ActionScriptBlittingPart3.as (line 62):
 
/** * Handles the timer */ private function enterFrameHandler(event:Event):void { tickPosition = int((getTimer() % 1000) / framePeriod); if (tickLastPosition != tickPosition) { tickLastPosition = tickPosition; canvas.lock(); canvas.fillRect(canvasClearRect, 0x000000); render(); canvas.unlock(); } }
The enterFrameHandler() method is fired on each ENTER_FRAME event. The code determines the number of seconds that have elapsed since the SWF started and divides this number by framePeriod, the period at which the game designer wants to render animation. Then if this value differs from the previous rendering frame event, the canvas is cleared and the animation is rendered.
The timing in Flash Player (sometimes described as an elastic race track) can vary and the arrival of the ENTER_FRAME event can fluctuate significantly across different machines. Combining the event and timing check enables smooth animation at a consistent rate, even if a machine is running faster than the SWF frame rate. Also if the SWF frame rate is increased, the animation frame rate can be kept at a consistent lower rate, allowing the CPU to process other logic without rendering each frame. Either way, you'll need to strike a balance between rendering consistency and CPU utilization needs. If you push the frame rate too high, slower machines may be unable to handle it.
When you run the ActionScriptBlittingPart3.as application, you should see a small circle rotating around the brown collector.
 
Moving the animation
The Flash Player MouseEvent.MOUSE_UP event provides the x and y coordinates on the Stage of the mouse click. Using the mouse click coordinates along with the collector's current x and y position, you can move the image to a different position on the canvas with each rendering. This gives the user the ability to move it around. The code below is an excerpt from the ActionScript class ActionScriptBlittingPart3.as (line 79) showing how the animation is moved around with blitting:
 
/** * Render any bitmap data. */ private function render():void { rect.x = (currentTile % 5) * 40; rect.y = int(currentTile / 5) * 40; collectorX += (destX-collectorX-20)/5; collectorY += (destY-collectorY-30)/5; canvas.copyPixels(spritesheet, rect, new Point(collectorX, collectorY), null, null, true); currentTile = ++currentTile % 20; } /** * Used to move the animation around. */ private function mouseUpHandler(event:MouseEvent):void { destX = event.stageX; destY = event.stageY; } }
The mouseUpHandler() method stores the x and y coordinates of the mouse click as the destination. Later the render() method determines a nonlinear delta between the collector's current position and the destination position. The delta is added back into the current position for a new position, which is then provided to the copyPixels() method as the blitting position.
If you click in the Stage near the running animation, the collector will move towards the location of your click.
 

 
Combining animations from multiple sprite sheets

The final example integrates multiple sprite sheets on a single Stage. In the previous example, you needed to store information about where to place the bitmap data in order to move the animation. When combining animations, you will need to keep track of more than just position. In addition to position, you may need to maintain and process depth level (to determine what bitmap is shown on top when two bitmaps overlap), animation state changes, differing animation frame rates, collision detection, and more.
In ActionScriptBlittingPart4.as, the collector will be on the lowest depth level. Randomly created colored gels fall from the top of the screen. If a gel collides with a collector, a third animation is run to show the gel melting on the collector.
 
Depth level
You probably have created animations in Flash that involve placing multiple objects on the Stage. You may have also used the mx.effects package to move or rotate an object. If objects overlapped, the z-index (depth in the display list) determined the order in which objects stacked up. While blitting, there is only one object being displayed: the destination bitmap. Since you are handling the rendering yourself, you will also need to keep track of the depth level of your objects and make sure everything is copied in the correct order.
To keep the falling colored gel animations on top of the collector, you must manage the order of blitting. In the render() method in ActionScriptBlittingPart4.as, all gel blitting (line 138 and 144) is done after the collector has been blitted (line 110).
 
Gel creation and metadata
Each colored gel is created at a random time in the enterFrameHandler() method. When a colored gel is created, createGel() sets its initial properties including a random x position, a zero y position, the default state, a zero meltFrame, and a unique name. The gel is then saves in gels, a Dictionary instance that the render() method loops through:
 
/** * Create a gel */ private function createGel():void { var gel:Object = new Object(); gel.posX = ((Math.random() * 0xffffff) % 280) + 20 gel.posY = 0; gel.state = "animate"; gel.meltFrame = 0; gel.name = "gel" + gelCount++; gels[gel.name] = gel; }
 
Gel logic and rendering states
The logic for moving the colored gel and changing to a melt state is located in the render() method:
 
/** * Render any bitmap data. */ private function render():void { rect.x = (currentTile % 5) * 40; rect.y = int(currentTile / 5) * 40; collectorX += (destX-collectorX-20)/5; collectorY += (destY-collectorY-30)/5; canvas.copyPixels(spritesheetCollector, rect, new Point(collectorX, collectorY), null, null, true); // Render Gel at half the frame rate, to slow it down if (currentTile % 2 == 1) { rect.x = ((currentTile-1) % 5) * 40; rect.y = int((currentTile-1) / 5) * 40; } for each (var gel:Object in gels) { // Hit Check 5 px within Y and X if (Math.abs(gel.posY - collectorY + 6) < 14 && Math.abs(gel.posX - collectorX) < 10) { gel.state = "melt"; } if (gel.state == "melt") { // Clear out if done melting if (gel.meltFrame < 20) gel.meltFrame++; else { delete gels[gel.name]; continue; } rect.x = (gel.meltFrame % 5) * 40; rect.y = int(gel.meltFrame / 5) * 40; canvas.copyPixels(spritesheetGelMelt, rect, new Point(collectorX-1, collectorY-12), null, null, true); continue; } else { canvas.copyPixels(spritesheetGel, rect, new Point(gel.posX, gel.posY), null, null, true); } gel.posY += 3; if (gel.posY > 320) { delete gels[gel.name]; continue; } } currentTile = ++currentTile % 20; }
The gels are animated at half the rate of the collector by resetting the rect value with currentTile-1 for odd numbered frames. Next, the code loops over all colored gels on the screen and checks for a collision with the collector. If there is a collision, it changes the state to "melt". In this state, render() uses the colored gel melt sprite sheet and runs off its own meltFrame count for 20 frames. Also when a collision has occurred the collector's x and y position are used for the melting animation, so it will follow the collector if it moves away. If there is no collision, the gel is moved down by three pixels. When its y position goes past 320, it is removed. As you see, with blitting you have to handle all of the animation logic.
 

 
Where to go from here

In this article, you've learned how to create a software blitting engine. You can use these techniques in other Flash Player rendering scenarios. You may want to explore different methods of creating source bitmap data used in the blitting process. For example, you can convert DisplayObject animation frames into a Bitmap and cache them into an array.