8bitrocket.com
9Feb/105

AS3 Tutorial: Controlling the Main Timeline of a SWF Embedded at Compile-Time

AS3 Tutorial: Controlling the Main Timeline of a SWF Embedded at Compile-Time

Today I was working a a new piece of the Essential Flash Games framework that allows sponsor swf files to be embedded at compile time and displayed before a game reaches the title screen. I am using Flash Develop and the Flex SDK, but the embed compiler directive is also available in the latest versions of the Flash IDE. I wanted to embed rather than load to ensure that the final swf file is a single self-contained unit that can be easily spread to game portals. I tried this a number of different ways to no avail. Most of the standard swf embed code works fine with assets inside the library of embedded swf, but not when trying to control the main time line of the swf itself. So, this is what we are going to cover: If you have been trying to embed swf files at compile time but cannot get your code to recognize the embedded swf, or the totalFrames attribute of the embedded swf returns 0, or if the swf only shows the final frame of your animation then read on. I had all of those problems today and more, but finally figured out a method to make this work properly.

Controlling the Main Time Line of a swf file embedded at run time.

1. Here is the asset that we are going to embed. I Embed all of my assets in a separate Library class as Static Const variables. This could just as easily be in the main document class for your game or application. I will do it the later way here to keep it uncomplicated. This assumes that the 8bitrocketlogo2.swf is in the same folder as the document class. This logo swf is a multi frame animation with 85 frames on the main time line (its just important that there is more than a single frame because you will want to see if you can play and jump to frames to ensure you have control over the time line).

[cc lang="javascript" width="550"]
[Embed(source = '8bitrocketlogo2.swf', mimeType="application/octet-stream")]
public static const LogoAnimation:Class;
[/cc]

This embeds the swf file as a BiteArrayAsset or a pure binary data stream. The mime-type of "application/octet-stream" is needed to embed as this data type.

2. Next we need to be sure to import the necessary classes

[cc lang="javascript" width="550"]
import flash.display.Loader;
import flash.display.MovieClip;
import mx.core.ByteArrayAsset;
import flash.events.Event;
[/cc]

3. We will need to create these class level variables

[cc lang="javascript" width="550"]
public var loader:Loader=new Loader;
public var logo:ByteArrayAsset;
public var clip:MovieClip;
[/cc]

4. Even though the swf file was embedded at compile time we need to use an asset Loader object to make use of it. First we must instantiate the LogoAnimation class we created that is effectively the equivalent of a "linkage name" in the IDE. We do this by assigning the logo variable to be a new instance of this embedded swf:

[cc lang="javascript" width="550"]
logo = new LogoAnimation();
[/cc]

5. We cannot access this data right away for a couple of reasons. First, the data inside this class is not yet an instance of MovieClip. It is still a ByteArray. Second, even though the asset swf has been embedded, we have no idea whether or not the code can access it yet. I found this out the hard way (hair pulling trial and error). I don't know the actual reason for this, but  I know a way to mitigate it. The best way (that I have found) to know when this embedded data is be ready to be used is to "load" it into an asset Loader class instance and check to see when it's INIT event has fired off. Fair enough. Add these lines to do just that:

[cc lang="javascript" width="550"]
loader.contentLoaderInfo.addEventListener(Event.INIT,loadCompleteListener,false,0,true);
loader.loadBytes(logo);
[/cc]

The contentLoaderInfo attribute of the Loader class will fire off the Event.INIT event when the Loader.content is ready to be used. Also notice that we have also called the loader.loadBytes method and passed in the instance of our embedded LogoAnimation class swf (the logo variable).

To be sure that the swf is completely ready before we try to access it, we will create the loadCompleteListener function to catch the INIT event.

[cc lang="javascript" width="550"]
private function loadCompleteListener(e:Event):void {
 
clip = MovieClip(loader.content);
addChild(clip);
clip.stop();
}
[/cc]

The clip variable in a MovieClip instance that we set to be the content of a MovieClip cast of the loader.content attribute. The loader.content contains the actual binary data of embedded swf but AS3 has no idea what to do with it. We cast it as a MovieClip and assign it to our clip variable so we can use this reference throughout our code without having to re-cast every time. The clip variable can now be used to control an instance of the embedded swf just like any other MovieClip instance.

When a swf is embedded all of the time line code is ignored. We need to call the clip.stop() function to make sure the clip stays on frame 1 until we want to actually play it. We also called the addChild(clip) function here, but it does not need to be called until you want to display the clip on the screen.

That's it. It was pretty simple after I finally got the hang of what was going on.

2Dec/090

AS2->AS3: Making A Click And Stick Christmas Tree Designer In AS2 and AS3

treeimage.jpg

In my last tutorial I focused on the differences in managing the mouse in AS2 and AS3 and how to create a click and drag effect in AS2 and AS3. This tutorial will take the code in those tutorials further to make a simple Christmas Tree designer in AS2 and AS3. This Christmas Tree designer is based (in part) on this viral Christmas Tree eCard designer I launched couple Decembers ago which, in turn, was a re-write of one of my first ever Flash applications I made in the year 2000. This Christmas season, instead of just re launching the same thing again, I've decided to try to show you, our faithful reader, how to make one yourself...in both AS2 and AS3.

The application we are going to create will do the following:

  1. Display a Christmas Tree graphic on the screen
  2. Display a red bulb and a green bulb on the screen
  3. Display a trash can on the screen
  4. Allow the user to click on the red or green bulb to create a red or green bulb
  5. Allow the user to move the bulbs using the "Click and Stick" method to decorate the Christmas tree.
  6. Allow the user to drag bulbs to the trash can to be deleted
Flash AS2

In The Library

The following assets are in the .fla library for this application:

  • Christmas Tree: a MovieClip exported as the text identifier tree
  • Red Bulb: a MovieClip with the graphic centered on the registration point, exported as the text identifier redbulb
  • Green Bulb: a MovieClip with the graphic centered on the registration point, exported as the text identifier greenbulb
  • Trash Can: a MovieClip exported as the text identifier trash

Creating The XmasAs2 Class

Since we are going to create a class that runs the application in AS2, we need to create an instance of that class in the first frame of the main timeline. The following code does that.

[cc lang="javascript" width="550"]game = new XmasAs2(this);
game._x = 0;
game._y = 0;
[/cc]

In the XmasAs2.as file, we will start by defining our class, and the class variables we will need for it. The variables we will use are as follows:

  • bulbArray: an array that will hold the list of bulbs the user has created and put on the screen
  • bulbCount: the number if bulbs the user has created. This is kept so we can create a new unique depth for each new object created
  • tree: A reference to the Christmas tree
  • trash: a reference to the trash can
  • greenbulb: A reference to the green bulb "button" users will click on to create new green bulbs.
  • redbulb: A reference to the red bulb "button" users will click on to create new red bulbs.
  • timeline: A reference to the main timeline to which we will attach MovieClips
  • parent: A dummy variable used by the bulbs as reference to the instance of XmasAs2 running the application.
  • dragging: A dummy variable used by bulbs to set if they are the bulb currently being dragged on the screen.
[cc lang="javascript" width="550"]
class XmasAs2 extends MovieClip {

var bulbArray:Array = new Array();
var bulbCount:Number;
var tree:MovieClip;
var trash:MovieClip;
var greenbulb:MovieClip;
var redbulb:MovieClip;
var timeline:MovieClip;
var parent:MovieClip;
var dragging:Boolean; }
[/cc]

Displaying Objects On The Screen/Setting Events And Callbacks

The constructor for XmasAs2 requires that a reference to the main timeline be passed to it. This is because we need a place to attach the objects we create from the library when we call attachMovie() for tree, greenbulb, redbulb, trash, and the bulbs we will create on user demand later.

We also need to set a dynamic variable in both redbulb and greenbulb named parent that is reference back to this instance of XmasAs2. Why? Well, because when redbulb and greenbulb are clicked, we need to call a function in XmasAs2 to create a new instance of a bulb. Since redbulb and greenbulb calla local function inside of XmasAs2 when their onPress event fires ( eventCreateNewBulb) that function will run in the context of those MovieClips, even though it lives in the XmasAs2 class. We need a simple way to get back to the instance of XmasAs2, and this is the way we do it. We could have created a Delegate to allow the buttons to run in their own context, or created the buttons as their own classes, but that would have created other issues and made the code even more complicated than it is already.

[cc lang="javascript" width="550"]
function XmasAs2(tl:MovieClip) {
timeline=tl;
tree = timeline.attachMovie("tree","tree1",1000);
trash = timeline.attachMovie("trash","trash1",500);
greenbulb = timeline.attachMovie("greenbulb","greenbulb1", 600);
redbulb = timeline.attachMovie("redbulb","redbulb1",700);
greenbulb.parent = this;
redbulb.parent = this;
tree._x = 150;
tree._y = 50;
trash._x =10;
trash._y = 300;
greenbulb._x = 450;
greenbulb._y = 100;
redbulb._x = 450;
redbulb._y = 150;
greenbulb.onPress = eventCreateNewBulb;
redbulb.onPress = eventCreateNewBulb;
bulbCount = 0;
}

[/cc]

 

Click And Stick Bulbs

When redbulb or greenbulb are clicked they spawn an event that then calls the eventCreateNewBulb() function, which in turn, calls parent.createNewBulb() in XmasAs2 passing a reference to itself (this). This gets out of the button context and back to the context the instance of XmasAs2.

createNBewBulb() uses the reference to redbulb or greenbulb passed to it to determine which bulb was clicked and which color bulb to create. It does this by checking the the _name property that was set was we used attachMovie() to create them. Depending on which one was clicked, we then use attachMovie() to create a new instance of the bulb. Since we need a running set of depths for each bulb we create, we create a function named getNextDepth() that uses bulbCount as a running total. It returns a depth that can be used for the object.

We then need to attach the newly created "bulb" to the mouse. This is the "stick" of the "click and stick". The bulb will stay attached until the user clicks the mouse button again. We do this by calling the startDrag() function of tempBulb. To make sure the bulb drops when the user clicks again (and can be picked up and moved later), we set the onPress listener to call the function dragTest. We also set the dynamic variable dragging to true (because the object is now being dragged), and create a dynamic variable for parent so we can reference the instance of XmasAS2 from the context of the bulb.

The dragTest() function is called whenever the user clicks on a bulb. This can happen when the user wants to drop a bulb that is stuck to the mouse or pick-up a bulb already on the screen. This function tests the dragging property of the bulb. If it is true, it calls stopDrag() set dragging to false, and then calls parent.testTrash(this) to see if the player has dropped the bulb on the trash can. If the dragging is false, we call startDrag(), and set the dragging property true.

[cc lang="javascript" width="550"] 
function eventCreateNewBulb() {
parent.createNewBulb(this);
}

function createNewBulb(bulb:MovieClip) {
var tempBulb:MovieClip;
var tempDepth = getNextDepth();
if (bulb._name == "greenbulb1") {
tempBulb = timeline.attachMovie("greenbulb","greenbulb" + tempDepth, tempDepth);
} else if (bulb._name == "redbulb1") {
tempBulb = timeline.attachMovie("redbulb","redbulb" + tempDepth, tempDepth);

}
tempBulb.onRelease = dragTest;
tempBulb.startDrag(true);
tempBulb.dragging = true;
tempBulb.parent = this;
bulbArray.push(tempBulb);

}

function dragTest() {

if (this.dragging) {
this.stopDrag();
this.dragging = false; parent.testTrash(this);
} else {

this.startDrag(true);
this.dragging = true;
}

}

function getNextDepth() {
bulbCount++;
return 2000+bulbCount;
}
[/cc]

 

Deleting Objects

When a user drops a bulb, we call testTrash() to see if the bulb was dropped on the trashcan. To do this ,we use the hitTest() function of the trashcan MovieClip to test to see if the bounding boxes of the bulb and trashcan are touching. If so, we loop through the bulbArray until we find the bulb dropped,use removeMoveClip() to get it off the screen, and then splice it out of the bulbArray.

[cc lang="javascript" width="550"]

function testTrash(bulb) {
if (bulb.hitTest(trash)) {
for (var i:Number = bulbArray.length-1; i >=0;i ) {
if (bulbArray[i] == bulb) {
bulb.removeMovieClip();
bulbArray.splice(i,1);
}
}
}
}
[/cc]

Full code

That's all there is to it. Here is the full code for the AS2 version.

[cc lang="javascript" width="550"]class XmasAs2 extends MovieClip {

var bulbArray:Array = new Array();
var bulbCount:Number;
var tree:MovieClip;
var trash:MovieClip;
var greenbulb:MovieClip;
var redbulb:MovieClip;
var timeline:MovieClip;
var parent:MovieClip;
var dragging:Boolean;

function XmasAs2(tl:MovieClip) {
timeline=tl;
tree = timeline.attachMovie("tree","tree1",1000);
trash = timeline.attachMovie("trash","trash1",500);
greenbulb = timeline.attachMovie("greenbulb","greenbulb1", 600);
redbulb = timeline.attachMovie("redbulb","redbulb1",700);
greenbulb.parent = this;
redbulb.parent = this;
tree._x = 150;
tree._y = 50;
trash._x =10;
trash._y = 300;
greenbulb._x = 450;
greenbulb._y = 100;
redbulb._x = 450;
redbulb._y = 150;
greenbulb.onPress = eventCreateNewBulb;
redbulb.onPress = eventCreateNewBulb;
bulbCount = 0;
}

function eventCreateNewBulb() {
parent.createNewBulb(this);
}

function createNewBulb(bulb:MovieClip) {
var tempBulb:MovieClip;
var tempDepth = getNextDepth();
if (bulb._name == "greenbulb1") {
tempBulb = timeline.attachMovie("greenbulb","greenbulb" + tempDepth, tempDepth);
} else if (bulb._name == "redbulb1") {
tempBulb = timeline.attachMovie("redbulb","redbulb" + tempDepth, tempDepth);

}
tempBulb.onRelease = dragTest;
tempBulb.startDrag(true);
tempBulb.dragging = true;
tempBulb.parent = this;
bulbArray.push(tempBulb);

}

function dragTest() {

if (this.dragging) {
this.stopDrag();
this.dragging = false;
parent.testTrash(this);
} else {

this.startDrag(true);
this.dragging = true;
}
;
}

function testTrash(bulb) {
if (bulb.hitTest(trash)) {
for (var i:Number = bulbArray.length-1; i >=0;i ) {
if (bulbArray[i] == bulb) {
bulb.removeMovieClip();
bulbArray.splice(i,1);
}
}
}
}

function getNextDepth() {
bulbCount++;
return 2000+bulbCount;
}
}
[/cc]

Test The Application

Flash AS3

In The Library

The following assets are in the .fla library for this application. the main difference from the AS2 application is that these are now class references and not text identifiers, and we have appended an "o" to the front of the names so the classes they they don't conflict with the variables we create for the objects.

  • Christmas Tree: a MovieClip exported as otree
  • Red Bulb: a MovieClip with the graphic centered on the registration point, exported as oredbulb
  • Green Bulb: a MovieClip with the graphic centered on the registration point, exported as ogreenbulb
  • Trash Can: a MovieClip exported as otrash

 

Creating The XmasAs3 Class

Unlike AS2 where we had to create an instance of the application class on the timeline, in AS3 this can happen automatically. All we need to to do is set the document property of the main timeline to: XmasAs3.

The XmasAs3.as file contains some important differences from it's AS2 counterpart. First, we need to specify a package in AS3, which is required for all classes. We also need to import the classes we are using before we specify the class identifier. However, because AS3 does things a bit more efficiently than AS2, we have fewer class properties to deal with. In fact, the main difference is that we don't need any variables to keep track of object depths (bulbCount), and we don't need those confusing dummy variables used by the bulb objects (parent, dragging), and we don't need a reference to the main timeline (timeline)because our class *is* the main timeline!

[cc lang="javascript" width="550"]package {
import flash.display.MovieClip;
import flash.ui.Mouse;
import flash.events.MouseEvent;

public class XmasAs3 extends MovieClip {

var bulbArray:Array = new Array();
var tree:MovieClip;
var trash:MovieClip;
var greenbulb:MovieClip;
var redbulb:MovieClip;

}

}[/cc]

Displaying Objects On The Screen / Setting Events And Callbacks

Compared to the constructor for XmasAs2, the constructor for XmasAs3 is clean and simple. We don't have to use attachMovie() in AS#, nor do we have to create arbitrary "depths" to assign to each object. Instead, we just create instances of our object using class instantiation notation (i.e. tree = new otree()), position them on the screen the same way we did in AS2 (except we use the .x and .y properties instead of ._x and ._y), and add them to the main timeline represented by this instance of XmasAs3 by calling this.addChild().

There are a few interesting differences thought that need to be highlighted. First, we are going to listen for the MOUSE_DOWN event on both redbulb and greenbulb because onPress does not exist any longer in AS3.

[cc lang="javascript" width="550"]public function XmasAs3() {

tree = new otree();
trash = new otrash();
greenbulb = new ogreenbulb();
redbulb = new oredbulb();
tree.x = 150;
tree.y = 50;
trash.x =10;
trash.y = 300;
greenbulb.x = 450;
greenbulb.y = 100;
redbulb.x = 450;
redbulb.y = 150;

this.addChild(tree);
this.addChild(trash);
this.addChild(greenbulb);
this.addChild(redbulb);

greenbulb.addEventListener(MouseEvent.MOUSE_DOWN, createNewBulb);
redbulb.addEventListener(MouseEvent.MOUSE_DOWN, createNewBulb);

greenbulb.clr = "green";
redbulb.clr ="red";
greenbulb.buttonMode = true;
greenbulb.useHandCursor = true;
redbulb.buttonMode = true;
redbulb.useHandCursor = true;
}
[/cc]

Click And Stick Bulbs

Clicking and sticking bulbs in AS3 is a bit more straight forward than in AS2. createNewBulb() is stilled called when redbulb or greenbulb are clicked, but instead of testing the _name property, we test a dynamic property named clr to that we set in the constructor. This could be taken even further to have separate class for the bulbs with an instance variable for clr.

After creating the proper instance of the bulb, this function operates very similar to the AS2 version. We add a listener for the MOUSE_DOWN event (because again, onPress does not exist in AS3), and we also set buttonMode and useHandCursor both to true to simulate the functionality of the AS2 buttons. We don't need to call getNextDepth() because this.addChild() does not require it, so we have omitted that function from the AS3 version.

The dragTest() function is also simpler and more straight forward in AS3 than in AS2. Since it is called from a mouse event, we get an instance of the MouseEvent class passed to the function, and it contains a magic property named target that represents the object clicked upon. Instead of having to pass around a reference to multiple functions, we can use this property. Also, in AS3 event callback functions execute in the context of the object that set them (XmasAs3), not the target object (bulb). This means we don't need to pass around a reference to parent, we just operate inside the function directly.

[cc lang="javascript" width="550"]public function createNewBulb(e:MouseEvent):void {
var tempBulb:MovieClip;
if (e.target.clr == "green") {
tempBulb = new ogreenbulb();
} else if (e.target.clr == "red") {
tempBulb = new oredbulb();

}
tempBulb.addEventListener(MouseEvent.MOUSE_DOWN, dragTest);
tempBulb.startDrag(true);
tempBulb.dragging = true;
tempBulb.buttonMode = true;
tempBulb.useHandCursor = true;
this.addChild(tempBulb);
bulbArray.push(tempBulb);

}

public function dragTest(e:MouseEvent):void {
if (e.target.dragging) {
e.target.stopDrag();
e.target.dragging = false;
} else {
e.target.startDrag(true);
e.target.dragging = true;
}
testTrash(MovieClip(e.target));
}
[/cc]

Deleting Objects

Deleting an object is AS3 is nearly identical to AS2. The main difference is that the hitTest() MovieClip method no longer exists in AS3. It has been replaced with hitTestObject() and hitTestPoint(). hitTestObject() is essentially the same as hitTest(), so we use that instead.

[cc lang="javascript" width="550"]public function testTrash(bulb:MovieClip):void {
if (bulb.hitTestObject(trash)) {
for (var i:Number = bulbArray.length-1; i >=0;i ) {
if (bulbArray[i] == bulb) {
this.removeChild(bulb);
bulbArray.splice(i,1);
}
}
}
}
[/cc]

Full code

And that's it! Here is the full code in AS3.

[cc lang="javascript" width="550"]package {
import flash.display.MovieClip;
import flash.ui.Mouse;
import flash.events.MouseEvent;

public class XmasAs3 extends MovieClip {

var bulbArray:Array = new Array();
var tree:MovieClip;
var trash:MovieClip;
var greenbulb:MovieClip;
var redbulb:MovieClip;

public function XmasAs3() {

tree = new otree();
trash = new otrash();
greenbulb = new ogreenbulb();
redbulb = new oredbulb();
tree.x = 150;
tree.y = 50;
trash.x =10;
trash.y = 300;
greenbulb.x = 450;
greenbulb.y = 100;
redbulb.x = 450;
redbulb.y = 150;

this.addChild(tree);
this.addChild(trash);
this.addChild(greenbulb);
this.addChild(redbulb);

greenbulb.addEventListener(MouseEvent.MOUSE_DOWN, createNewBulb);
redbulb.addEventListener(MouseEvent.MOUSE_DOWN, createNewBulb);

greenbulb.clr = "green";
redbulb.clr ="red";
greenbulb.buttonMode = true;
greenbulb.useHandCursor = true;
redbulb.buttonMode = true;
redbulb.useHandCursor = true;

}

public function createNewBulb(e:MouseEvent):void {
var tempBulb:MovieClip;
if (e.target.clr == "green") {
tempBulb = new ogreenbulb();
} else if (e.target.clr == "red") {
tempBulb = new oredbulb();

}
tempBulb.addEventListener(MouseEvent.MOUSE_DOWN, dragTest);
tempBulb.startDrag(true);
tempBulb.dragging = true;
tempBulb.buttonMode = true;
tempBulb.useHandCursor = true;
this.addChild(tempBulb);
bulbArray.push(tempBulb);

}

public function dragTest(e:MouseEvent):void {
if (e.target.dragging) {
e.target.stopDrag();
e.target.dragging = false;
} else {
e.target.startDrag(true);
e.target.dragging = true;
}
testTrash(MovieClip(e.target));
}

public function testTrash(bulb:MovieClip):void {
if (bulb.hitTestObject(trash)) {
for (var i:Number = bulbArray.length-1; i >=0;i ) {
if (bulbArray[i] == bulb) {
this.removeChild(bulb);
bulbArray.splice(i,1);
}
}
}
}

}
}
[/cc]

Test The Application

Dowload the full code for this tutorial here.

 

 

1Dec/090

AS2->AS3: A Simple Drag And Drop Game plus Hiding/Showing/Changing The Mouse Pointer

One of the things designers love to do in the Flash IDE is change the mouse pointer. This was a pretty simple task in AS2, but in AS3 there are few complications. The following short tutorial describes how to hide/show/change the mouse pointer in both AS2 and AS3. It then takes the concept one step further to discuss dragging and dropping objects in AS2 and AS3

Let's say that you want to create the following Flash application:

1. When you press the mouse button, the mouse pointer disappears and is replaced with a cross hairs

2. When you let the mouse-button up, the cross hairs disappears, and the mouse pointer reappears.

One of the nice things about AS3 in this case, is that the basic functions are very similar, but the implementation is quite different.

Flash AS2

For this example, we have created a "crosshairs" graphic as a MovieClip in the library and exported it as "pointer". The most important functions for hiding the mouse, showing the mouse, and changing the mouse pointer are as follows:

  • Mouse.hide() : Hides the mouse pointer from the screen using the global Mouse object.
  • Mouse.show() : Displays the mouse pointer on the screen by using the global Mouse object.
  • [MovieClip].startDrag(): A MovieClip method that begins the mouse dragging the MovieClip
  • [MovieClip].stopDrag(): A MovieClip method that stops the mouse dragging the MovieClip

In Flash AS2, you might write the code this way and put it directly in your .fla.

[cc lang="javascript" width="550"]var mouseptr:MovieClip = this.attachMovie("pointer","crosshairs",1000);
mpouseptr._visible = false;

this.onMouseDown = function() {
hideMouse();
}

this.onMouseUp = function() {
showMouse();
}

function showMouse() {
mouseptr._visible = false;
mouseptr.stopDrag();
Mouse.show();
}

function hideMouse() {
mouseptr._visible = true;
mouseptr.startDrag(true);
Mouse.hide();
}
[/cc]

 

Flash AS3

However, in AS3 we would want to create a document class for the application, and then modify the same code so that it is AS3 compatible. The good news, is that all the basic functions (Mouse.hide(), Mouse.show(), MovieClip.startDrag(), MovieClip.stopDrag()) work nearly the same in AS3 as they did in AS2. However, there are several differences that need to be highlighted

  1. MouseAS3 is set as the Document class for this .fla in the Flash IDE. That means it will be automatically created when the .SWF runs.
  2. Instead of using the onMouseXXX events from AS2, we need to use the addEventListener() method of stage, and listen for both MouseEvent.MOUSE_DOWN and MouseEvent.MOUSE_UP
  3. Where we used the "_visible" property of the MovieClip in AS2, we can use this.addChild() and this.removeChild() in AS3 for a cleaner program.
  4. Instead of exporting "pointer" in the Flash library, we set it's class to be "pointer". This means we can simply instantiate it with "new pointer()" instead of using the clumsy "MovieClip.attachMovie()" from AS2.

Here is what the code might look like in AS3.

 

[cc lang="javascript" width="550"]package
{
import flash.display.MovieClip;
import flash.ui.Mouse;
import flash.events.MouseEvent;

public class MouseAS3 extends MovieClip
{
var mouseptr:MovieClip = new pointer();

public function MouseAS3() {
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDownEvent);
stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUpEvent);
}

public function onMouseDownEvent(e:MouseEvent) {
hideMouse();
}

public function onMouseUpEvent(e:MouseEvent) {
showMouse();
}

public function showMouse() {
removeChild(mouseptr);
mouseptr.stopDrag();
Mouse.show();
}

public function hideMouse() {
addChild(mouseptr);
mouseptr.startDrag(true);
Mouse.hide();
}
}
}
[/cc]

 

A Simple Drag And Drop Game In AS2 and AS3

Now that we can make a simple application that hides and shows the mouse in AS3, plus changes the mouse pointer, in both AS2 and AS3, why don't we take it step further and do something useful with this concept: create a simple drag and drop game.

The "game" randomly creates 10 instances of the "pointer", and then allows the player to move them around. It really is not much of a game, but the basis of it could be used for a multitude of different applications.

 

Flash AS2

In AS2, creating a "Drag and Drop" style game was very easy, but suffered from some inconsistencies that made the process a little on the bizarre side. Just for your own edification, I will describe the two major types of "drag and drop" patterns, and how they differ:

Click And Drag: When a user clicks on an item, holds the mouse down to drag it, and then releases the mouse button to drop it. This is the type of drag and drop we are going to simulate here.

Click And Stick: When a user clicks on an item and releases the mouse button, but the item continues to be dragged until the user clicks the mouse again. We want to avoid "click and stick" for this demonstration because in practice, users are much more comfortable with Click And Drag,

In AS2, to simulate Click And Drag, you needed to use the onPress and onRelease events of a MovieClip. The onPress event is fired when the user clicks the mouse button down, and the onRelease event is fired then the button is let-up. Only while the button is down, the user can move the object around, and thus you get the "Click And Drag" effect. To create this effect, we create two functions, dragObject() and dropObject() and set the onPress and onRelease call-back functions to each accordingly. Since Flash AS2 scoped call-back function to the object that created the event, not the object container, all we had to do was call this.startDrag() and this.stopDrag() in the respective functions, to get the desired effect.

The possible AS2 code for creating application of this type is below:

[cc lang="javascript" width="550"]var objectArray:Array = new Array();
for (var i =0;i<10;i++) {
objectArray[i] = this.attachMovie("pointer","crosshairs"+i,1000+i);
objectArray[i]._x = Math.floor(Math.random() * 550);
objectArray[i]._y = Math.floor(Math.random() * 400);
objectArray[i].onPress = dragObject;
objectArray[i].onRelease = dropObject;
}

function dropObject() {
this.stopDrag();
}

function dragObject() {
this.startDrag(true);
}

[/cc]

Flash AS3

To create the "Click And Drag" game in AS3, we need to do nearly everything we did for the changing the mouse in AS3, and take it a few steps further.

  1. We create a document class, this time named MouseDADAS3.
  2. We still use a for:next loop to create the 10 objects, but we need to make many changes inside the loop for AS3:
    • Use"new pointer() instead of attachMovie().
    • Use the .x and .y properties of each MovieClip, not ._x or ._y as we did in AS2
    • Add Event Listeners for both MouseEvent.MOUSE_DOWN and MouseEvent.MOUSE_UP, because onPress and onRelease no longer exists in AS3.
    • Set buttonMode and useHandCursor to true for each object so that they will act like the objects in the AS2 version and have a hand show-up when the mouse pointer rolls over them.
  3. Since the call-back function in AS3 are no longer scoped to the object that created them, we cannot simply call this.startDrag() and this.stopDrag(). Instead, we need to use the target attribute of the MouseEvent passed to the listener function when the event is created. The target represents the object that created the event. We can use target just like we used this in the AS2 version.

 

[cc lang="javascript" width="550"]package
{
import flash.display.MovieClip;
import flash.ui.Mouse;
import flash.events.MouseEvent;

public class MouseDADAS3 extends MovieClip
{
var objectArray:Array = new Array();

public function MouseDADAS3() {
for (var i =0;i<10;i++) {
objectArray[i] = new pointer();
objectArray[i].x = Math.floor(Math.random() * 550);
objectArray[i].y = Math.floor(Math.random() * 400);
objectArray[i].addEventListener(MouseEvent.MOUSE_DOWN, dragObject);
objectArray[i].addEventListener(MouseEvent.MOUSE_UP, dropObject);
objectArray[i].buttonMode = true;
objectArray[i].useHandCursor = true;
this.addChild(objectArray[i])
}
}

function dropObject(e:MouseEvent) {
e.target.stopDrag();

}

function dragObject(e:MouseEvent) {
e.target.startDrag(true);

}

}
}
[/cc]


And that is all there is to it. This simple dragging "game" could be easily turned into a multitude of different actual games like matching , dress-up, object manipulation contests, and many other concepts. Watch out for a possible "part 2" of this tutorial that explores some of those ideas.

You can download the source code for this tutorial here: as2toas3mouse.zip

 

3Aug/090

Clayton Grey's Blit Layer Erase Speed Test

Clayton Grey's Blit Layer Erase Speed Test

A super diligent and incredibly intelligent (aren't you all) 8bitrocket
reader (and now contributor), Clayton Grey has been running some tests
on blitting entire screen-sized portions of BitmapData to clear the
screen and re-paint.   He doesn't have a blog of his own, and
asked that we present his findings. I am more than happy to because I
have been meaning to do the exact same test.  Clayton 
found that clearing an entire blit canvas can actually take
more time than the actual re-blit of the game screen layer. Because of
this, he tested as many methods of clearing the screen as possible and
found that copyPixels is still the king . That's great,  as I
have always relied on copyPixels for my blit erase operations.

Here are the results of Clayton's first round of tests: I've
left out his comments on the fact that he had to do this because I
haven't done any tutorials in a while. That would be because all of my
current best stuff is going into a new book and I will start new
tutorials after it has been finalized and sent to the publisher.

The results are timed in milliseconds with a getTimer() and the
beginning and end of the test (full source is below).

Tests
Round 1

My test results are as
follows on an 8-core Mac Pro:

For
1,000 iterations:

Fill w/ Alpha: 231
Fill w/o Alpha: 230
Copy + Alpha Merge True: 108
Copy + Alpha Merge False: 126

For
100,000 iterations:

Fill w/ Alpha: 23457
Fill w/o Alpha: 24056
Copy + Alpha Merge True: 11294
Copy + Alpha Merge False: 12229


Copying a blank with alpha merge is the clear winner here (though the
difference between alpha merge on and off is marginal) there an ~205%
increase in speed for blitting an empty canvas. I re-ran the tests once
more with a final step of filling the "blank" with a color (which I
imagine could be any background...)

For
1,000 iterations:

Fill w/ Alpha: 231
Fill w/o Alpha: 230
Copy + Alpha Merge True: 107
Copy + Alpha Merge False: 114
Filled Blank Copy + Alpha Merge True: 107
Filled Blank Copy + Alpha Merge False: 124


For
100,000 iterations:

Fill w/ Alpha: 23425
Fill w/o Alpha: 24036
Copy + Alpha Merge True: 11213
Copy + Alpha Merge False: 12152
Filled Blank Copy + Alpha Merge True: 11303
Filled Blank Copy + Alpha Merge False: 11591

Pretty similar. As such even if you're using a solid background color,
unless it's some sort of animated gradient, you're better off caching
it in a a bitmap "blank".


Test
Round 2

1,000 Iterations:

setPixels() with Lock: 2162
setPixels() without Lock: 2161
draw() with Lock: 768
draw() without Lock: 755
clone() with lock: 2390
clone() without lock: 2354
fillRect() w/ Alpha w/ Lock: 249
fillRect() w/ Alpha w/o Lock: 241
fillRect() w/o Alpha w/ Lock: 233
fillRect() w/o Alpha w/o Lock: 231
copyPixels() + Alpha Merge True w/ Lock: 106
copyPixels() + Alpha Merge True w/o Lock: 105
copyPixels() + Alpha Merge False w/ Lock: 133
copyPixels() + Alpha Merge False w/o Lock: 135
Filled Blank copyPixels() + Alpha Merge True w/ Lock: 104
Filled Blank copyPixels() + Alpha Merge True w/o Lock: 104
Filled Blank copyPixels() + Alpha Merge False w/ Lock: 126
Filled Blank copyPixels() + Alpha Merge False w/o Lock: 130

I did each method once with a lock() and unlock() as well as without (
just for kicks ). Again, I'm including the source. If you can think of
any other ways to fill BitmapData with empty pixels, add 'em.
copyPixels is still the fastest game in town.

Full
source


[cc lang="javascript" width="550"]

package com.laboratory.util
{
import flash.display.BitmapData;
import flash.geom.Point;
import flash.utils.getTimer;

public class FillOrCopyTest
{
public function FillOrCopyTest()
{
var _cache : BitmapData = new BitmapData( 640, 480, true, 0x000000 );
var _blank : BitmapData = new BitmapData( 640, 480, true, 0xFF0000 );
var _point : Point = new Point();

var i : int;

var time : int;

time = getTimer();

for ( i = 0; i < 100000; ++i )
{
_cache.fillRect( _cache.rect, 0x00000000 );
}

time = getTimer() - time;
trace( "Fill w/ Alpha: " + time );

time = getTimer();

for ( i = 0; i < 100000; ++i )
{
_cache.fillRect( _cache.rect, 0xFF0000 );
}

time = getTimer() - time;
trace( "Fill w/o Alpha: " + time );

time = getTimer();

for ( i = 0; i < 100000; ++i )
{
_cache.copyPixels( _blank, _blank.rect, _point , null, null, true );
}

time = getTimer() - time;
trace( "Copy + Alpha Merge True: " + time );

time = getTimer();

for ( i = 0; i < 100000; ++i )
{
_cache.copyPixels( _blank, _blank.rect, _point , null, null, false );
}

time = getTimer() - time;
trace( "Copy + Alpha Merge False: " + time );

_blank.fillRect( _blank.rect, 0xFF0000 );

time = getTimer();

for ( i = 0; i < 100000; ++i )
{
_cache.copyPixels( _blank, _blank.rect, _point , null, null, true );
}

time = getTimer() - time;
trace( "Filled Blank Copy + Alpha Merge True: " + time );

time = getTimer();

for ( i = 0; i < 100000; ++i )
{
_cache.copyPixels( _blank, _blank.rect, _point , null, null, false );
}

time = getTimer() - time;
trace( "Filled Blank Copy + Alpha Merge False: " + time );
}
}
}

[/cc]



What do you think of Clayton's tests? Pretty comprehensive, I think. If
you have any questions or comments, leave them below. Clayton will be
around to answer them or he might even contact you if you want to offer
more advice or have questions that won't fit in the comment box.

3May/090

Tutorial: Clearing a blit canvas by erasing only the portions that have changed (using damage maps or a dirty rect).

Tutorial: Clearing a blit canvas by erasing only the portions that have
changed. This is commonly called damage mapping or  dirty rect processing.

(Squize called this a Damage Map. I have heard that before but didn't know that it was basically what I was doing here).

That's a mouthful, but I had trouble coming up with a shorter, more
informative title...

DAMAGE MAPPING!

I am currently working on a mini-retro remake of the game Star Castle
called Solar
Fortress
. I am at the point in my code design where I need
to figure out how I am going to render my particle and special effects
to the screen. Most of the current game objects are standard sprites
with BitmapData making up their look (an embedded Bitmap Object with a
BitmapData attached). For the the special effects and particles, I am
planning to blit to a layer on top of the main screen. I was about to
start writing this code as I would any standard set of blit operations
when I was side tracked by an old book on game programming my Andre
LaMothe.

In this classic book on DOS games, Andre
spills the beans on a number of classic game programming secrets such
as blitting, sprites, image compression (RLE), transparency, and much
more.
While waiting for Steve to finish up a conference call, I sat at his
desk perusing this classic gem. In his chapter on sprites and
blitting, Andre wrote about a method for refreshing the a blit screen
that I had never tried. Usually, I refresh the entire viewable blit
canvas with the background layer before I start to blit the individual
sprites to the canvas. Andre's method was quite different.

Inside the loop where he updates and blits each object, he first copies
the background under object and replaces the foreground screen location
of the object with the background, effectively erasing the object only.
Then, he copies the object to its new location on the screen. Thus, he
only  updates the screen portions that have changed.

I decided to test this method in AS3 against my normal copyPixels
screen erase and against a method of simply
clearing the background with a solid color using the
fillRect()
of the BitmapData object.

Below is a swf with the results of the tests. This swf blits 6,000
moving 10x10 squares to the screen
using my (and Chris Cutler's)  optimized
sleep based active timer
. It runs though each of the
three
methods for 100 frames. Before and after each frame it uses a
getTimer()
operation to count the milliseconds passed and adds that to a total.
After 100 frames it divides that total number by 100 to get the average
number of milliseconds. It does this separately  for each of
the three different methods.

This
was written as a very quick and dirty test. The results in the
browsers and players I have seen generally show that the new method
(erase just the
portion that has changed)  to run faster than the other 2
methods.
They are all very close though.  This is a little surprising
to me because with 6,000 objects I would have thought the extra 5,999
blit
operations (although small) would take more more time than the one full
screen erase operation. If you run the test multiple times you will get
slightly varying results. Generally though, they methods rank like this:

Fastest: to Slowest:

1. Just erase screen parts that have changed

2. FillRect() with a solid color - unusable though if you background is
NOT a solid color or is a transparent layer on top of the game player
(like mine will be).

3. Use copyPiixels to re-draw the entire back ground each frame.

Here is an explanation of the three methods. If you need some basics
on my blitting code, try
this tutorial
. After these explanations, the
swf has been embedded for you to try. The entire main class follows
that so you can test out the code if you desire.

Method 1: CopyPixels
Entire Background

In this method (my normal method), at the start of each render cycle, I
erase the blit canvas by copying the entire background BitmapData to
it. I then blit each object into its new position.   With 6000
objects, this equals 6001 blit operations per frame cycle.

[cc lang="javascript" width="550"]canvasBD.copyPixels(backgroundBD, backgroundBD.rect,backgroundPoint);[/cc]
Then update each object and blit it to to the canvasBD
 

(loop through objects for run this code for
each)

[cc lang="javascript" width="550"]
blitPoint.x = tempObject.x;
blitPoint.y = tempObject.y;
canvasBD.copyPixels(objectBD, objectBD.rect, blitPoint);[/cc]

Method 2: fillRect()

This method is only possible because I have a solid back background. I
would not be able to use the BitmapData.fillRect() method with a
detailed
background. There is a way to use the Sprite.beginBitmapFill() method,
but that was out of the scope of this test. It also would have been
invalid because it would have required a different canvas for the
Background (a separate Sprite). In my version of  this method,
at the start of the render cycle I simply fill the background canvas
with black and then do the 6000 blit operations.
 

[cc lang="javascript" width="550"]canvasBD.fillRect(canvasBD.rect, 0xFF000000);[/cc]
Then update each object and blit it to to the canvasBD:
 

(loop through objects for run this code for
each)

[cc lang="javascript" width="550"]
blitPoint.x = tempObject.x;
blitPoint.y = tempObject.y;[/cc]
canvasBD.copyPixels(objectBD, objectBD.rect, blitPoint);[/cc]

Method 3: Erase Updated
Background Only

In this method, I do NOT erase or fill the background canvas before I
begin the 6000 blit operations. I actually do 12,000 blit operations.
For each object I first blit the the background under it to its current
position as an eraser, then update the object's position and then blit
back into its new location.
 

Before Objects are updated

(loop through objects for run this code for
each)

[cc lang="javascript" width="550"]
blitPoint.x = tempObject.x;
blitPoint.y = tempObject.y;
canvasBD.copyPixels(backgroundBD, objectBD.rect, blitPoint);
 [/cc]

Then update the  object positions, animation frame, etc and
then run this code:

[cc lang="javascript" width="550"]
blitPoint.x = tempObject.x;
blitPoint.y = tempObject.y;
canvasBD.copyPixels(objectBD, objectBD.rect, blitPoint);
[/cc]

What is it doing:  
While looping though all of the objects in the array (of particles for instance), it first blits from the background (backgroundBD) to the viewable canvas (canvasBD)

Next, it assumes that your code updates the position and or animation frame of the object and then it blits the bitmapData containing the object (objectBD in this example) to the canvas (canvasBD).


Results

Generally, I have found a 2 or 3 millisecond average speed increase per
frame tick by just erasing the portion of the screen for each object
that has changed. Also, the fillRect() operations seems to be a
little faster (generally) than the copyPixels operation.  This
is the result for 6000 objects, using my optimized timer for 100
frames.
I have provided the code so you can test it out (if you desire) with
more or less objects and more or less frames. I have found that the
results are a little different with fewer objects. With just 1-50
objects on the screen, Method 3 seems to be much faster, but as you add
objects (under 1000), the results are mixed. When you get up to 5000,
clearly the Erase Only the Updated Portion (Method 3) comes out on top
more often.

Still, I don't see using any of these methods as a problem. I
am going to use the new method for my current game though just because
it is new and I can.

Test it for your self and let me know if you see similar results.

 

[cc lang="javascript" width="550"]
package
{
import flash.display.*;
import flash.text.*;
import flash.geom.*;
import flash.events.*;
import flash.events.TimerEvent;
import flash.utils.Timer;
import flash.utils.getTimer;
/**
* ...
* @author Jeff Fulton
*/
public class Main extends Sprite
{
 

private var mode:Function;
//gameTimer
public static const FRAME_RATE:int = 40;
public var _period:Number = 1000 / FRAME_RATE;
public var _beforeTime:int = 0;
public var _afterTime:int = 0;
public var _timeDiff:int = 0;
public var _sleepTime:int = 0;
public var _overSleepTime:int = 0;
public var _excess:int = 0;
public var gameTimer:Timer;
private var rformat:TextFormat = new TextFormat("_sans","12","0xffffff","true");
private var messagetext:TextField = new TextField();
private var aKeyPress:Array=[];

private var backgroundBD:BitmapData = new BitmapData(400, 400, false, 0x000000);
private var canvasBD:BitmapData = new BitmapData(400, 400, true, 0xFF000000);
private var canvasBitmap:Bitmap = new Bitmap(canvasBD);

private var objectBD:BitmapData = new BitmapData(5, 5, false, 0xff0000);
private var aObject:Array = [];
private var tempObject:Object;
private var blitPoint:Point = new Point();
private var backgroundPoint:Point = new Point(0, 0);

private var startTime:Number;
private var endTime:Number;
private var totalTime:Number;

private var resultCopypixels:Number;
private var resultBitmapfill:Number;
private var resultBliterase:Number;

private var numObjects:int = 6000;
private var numFrames:int = 100;
private var frameCtr:int = 0;

public function Main()
{

for (var ctr:int = 0; ctr < numObjects; ctr++) {
//trace("creating object: " + ctr);
var tempObjectCreate:Object = new Object();
tempObjectCreate.x = 0;
tempObjectCreate.y = 0;
tempObjectCreate.dx = 0;
tempObjectCreate.dy = 0;
tempObjectCreate.bitmapData = objectBD;
aObject.push(tempObjectCreate);
}

addChild(canvasBitmap);
mode = modeMenuSetup;
//gameTimer=new Timer(_period,0);
gameTimer=new Timer(_period,1);
gameTimer.addEventListener(TimerEvent.TIMER, runGame);
gameTimer.start();
}

public function runGame1(e:TimerEvent):void {
mode(0);
e.updateAfterEvent();
}

public function runGame(e:TimerEvent):void {
//trace("run game");
_beforeTime = getTimer();
_overSleepTime = (_beforeTime - _afterTime) - _sleepTime;

//****run the current system function
mode(0);
//***********************************

_afterTime = getTimer();
_timeDiff = _afterTime - _beforeTime;
_sleepTime = (_period - _timeDiff) - _overSleepTime;
if (_sleepTime <= 0) {
_excess -= _sleepTime
_sleepTime = 2;
}
gameTimer.reset();
gameTimer.delay = _sleepTime;
gameTimer.start();
while (_excess > _period) {
//****run the current system function
mode(1);
//***********************************

_excess -= _period;
}

e.updateAfterEvent();
}
private function modeMenuSetup(updatetype:int):void {
stage.addEventListener(KeyboardEvent.KEY_DOWN,keyDownListener);
stage.addEventListener(KeyboardEvent.KEY_UP, keyUpListener);
messagetext.width = 250;
messagetext.height = 200;
messagetext.x = 60;
messagetext.y = 100;
messagetext.text = "This will test 3 different methods of
\nrefreshing the background\n
after a set of blit operations\n\nPress Space to Start Test";
rformat.align = "center";
messagetext.setTextFormat(rformat);
addChild(messagetext);

mode = modeMenuRun;

}

private function modeMenuRun(updatetype:int):void {
if (aKeyPress[32]) {
trace("space pressed");
mode = modeSetupCopypixels;
}
}

private function modeSetupCopypixels(updatetype:int):void {

for (var ctr:int = 0; ctr < numObjects;ctr++ ) {
tempObject = aObject[ctr];
tempObject.x = (Math.random() * 399);
tempObject.y = (Math.random() * 399);
var randInt:int=int(Math.random()*36);
tempObject.dx=Math.cos(2.0*Math.PI*((randInt*10)-90)/360.0);
tempObject.dy = Math.sin(2.0 * Math.PI * ((randInt * 10) - 90) / 360.0);
}
//trace("all object reset");
//trace("object 1 x=" + aObject[1].x);
messagetext.text="Running copyPixels test"
messagetext.setTextFormat(rformat);
frameCtr = 0;
resultCopypixels = 0;
totalTime = 0;
mode = modeRunCopypixels;
}

private function modeRunCopypixels(updateType:int):void {
startTime = getTimer();
switch (updateType) {

case 0:
backgroundBD.lock();
drawCopypixelsBackground();
updateAndDrawCopypixelsObjects();
backgroundBD.unlock();
break;
case 1:
updateAndDrawCopypixelsObjects();
break;

}
endTime = getTimer();
totalTime += (endTime-startTime);
frameCtr++;
//trace("frameCtr="+frameCtr);
if (frameCtr == numFrames) {

resultCopypixels = (totalTime/numFrames);
trace("copypixels result=" + resultCopypixels);
mode = modeSetupBitmapfill;
}
}

private function updateAndDrawCopypixelsObjects():void {

//trace("object 1 again x=" + aObject[1].x);
for (var ctr:int = 0; ctr < numObjects;ctr++ ) {
tempObject = aObject[ctr];
tempObject.x += tempObject.dx;
tempObject.y += tempObject.dy;
if (tempObject.x > 399) {
tempObject.x = 0;
}else if (tempObject.x<0) {
tempObject.x = 399;
}

if (tempObject.y > 399) {
tempObject.y = 0;
}else if (tempObject.y<0) {
tempObject.y = 399;
}

blitPoint.x = tempObject.x;
blitPoint.y = tempObject.y;
//trace("blitPoint=" + blitPoint.toString());
canvasBD.copyPixels(objectBD, objectBD.rect, blitPoint);

}

}

private function drawCopypixelsBackground():void {
canvasBD.copyPixels(backgroundBD, backgroundBD.rect,backgroundPoint);
}

private function modeSetupBitmapfill(updatetype:int):void {
for (var ctr:int = 0; ctr < numObjects;ctr++ ) {
tempObject = aObject[ctr];
tempObject.x = (Math.random() * 399);
tempObject.y = (Math.random() * 399);
var randInt:int=int(Math.random()*36);
tempObject.dx=Math.cos(2.0*Math.PI*((randInt*10)-90)/360.0);
tempObject.dy = Math.sin(2.0 * Math.PI * ((randInt * 10) - 90) / 360.0);
}
messagetext.text = "Running fillRect test";
messagetext.setTextFormat(rformat);
frameCtr = 0;
resultBitmapfill = 0;
totalTime = 0;
mode = modeRunBitmapfill;
}

private function modeRunBitmapfill(updateType:int):void {
startTime = getTimer();
switch (updateType) {

case 0:
backgroundBD.lock();
drawBitmapfillBackground();
updateAndDrawBitmapfillObjects();
backgroundBD.unlock();
break;
case 1:
updateAndDrawBitmapfillObjects();
break;

}
endTime = getTimer();
totalTime += (endTime-startTime);
frameCtr++;
//trace("frameCtr="+frameCtr);
if (frameCtr == numFrames) {
resultBitmapfill =(totalTime/numFrames);
trace("bitmapfill result=" + resultBitmapfill);
mode = modeSetupBliterase;
}
}

private function updateAndDrawBitmapfillObjects():void {

//trace("object 1 again x=" + aObject[1].x);
for (var ctr:int = 0; ctr < numObjects;ctr++ ) {
tempObject = aObject[ctr];
tempObject.x += tempObject.dx;
tempObject.y += tempObject.dy;
if (tempObject.x > 399) {
tempObject.x = 0;
}else if (tempObject.x<0) {
tempObject.x = 399;
}
if (tempObject.y > 399) {
tempObject.y = 0;
}else if (tempObject.y<0) {
tempObject.y = 399;
}

blitPoint.x = tempObject.x;
blitPoint.y = tempObject.y;
//trace("blitPoint=" + blitPoint.toString());
canvasBD.copyPixels(objectBD, objectBD.rect, blitPoint);

}

}

private function drawBitmapfillBackground():void {
canvasBD.fillRect(canvasBD.rect, 0xFF000000);
}

private function modeSetupBliterase(updatetype:int):void {
for (var ctr:int = 0; ctr < numObjects;ctr++ ) {
tempObject = aObject[ctr];
tempObject.x = (Math.random() * 399);
tempObject.y = (Math.random() * 399);
var randInt:int=int(Math.random()*36);
tempObject.dx=Math.cos(2.0*Math.PI*((randInt*10)-90)/360.0);
tempObject.dy = Math.sin(2.0 * Math.PI * ((randInt * 10) - 90) / 360.0);
}
messagetext.text = "Running bliterase test";
messagetext.setTextFormat(rformat);
frameCtr = 0;
resultBliterase = 0;
totalTime = 0;
startTime = getTimer();
mode = modeRunBliterase;
}
private function modeRunBliterase(updateType:int):void {
startTime = getTimer();
switch (updateType) {

case 0:
backgroundBD.lock();
drawBitmapfillBackground();
updateAndDrawBitmapfillObjects();
backgroundBD.unlock();
break;
case 1:
updateAndDrawBitmapfillObjects();
break;

}
endTime = getTimer();
totalTime += (endTime-startTime);
frameCtr++;
//trace("frameCtr="+frameCtr);
if (frameCtr == numFrames) {

endTime = getTimer();
resultBliterase =(totalTime/numFrames);
trace("bliterase result=" + resultBliterase);
mode = modeResultsSetup;
}
}

private function updateAndDrawBliteraseObjects():void {

//trace("object 1 again x=" + aObject[1].x);
for (var ctr:int = 0; ctr < numObjects; ctr++ ) {
blitPoint.x = tempObject.x;
blitPoint.y = tempObject.y;
canvasBD.copyPixels(backgroundBD, objectBD.rect, blitPoint);
tempObject = aObject[ctr];
tempObject.x += tempObject.dx;
tempObject.y += tempObject.dy;
if (tempObject.x > 399) {
tempObject.x = 0;
}else if (tempObject.x<0) {
tempObject.x = 399;
}

if (tempObject.y > 399) {
tempObject.y = 0;
}else if (tempObject.y<0) {
tempObject.y = 399;
}

blitPoint.x = tempObject.x;
blitPoint.y = tempObject.y;
canvasBD.copyPixels(objectBD, objectBD.rect, blitPoint);

}

}

private function modeResultsSetup(updateType:int):void {
canvasBD.copyPixels(backgroundBD, backgroundBD.rect,backgroundPoint);

messagetext.text = "Results (avg milliseconds):\nCopypixels: " + resultCopypixels +
" milliseconds\nBitmapfill: " + resultBitmapfill + " milliseconds\nBliterase: " +
resultBliterase + " milliseconds"+"\n\n[R] to run again.";
messagetext.setTextFormat(rformat);
mode = modeResultsrun;
}

private function modeResultsrun(updateType:int):void {
if (aKeyPress[82]) {
for (var ctr:int = 0; ctr < numObjects;ctr++ ) {
tempObject = aObject[ctr];
tempObject = null;
}
mode = modeMenuSetup;
}

}

private function keyDownListener(e:KeyboardEvent):void {
trace(e.keyCode);
aKeyPress[e.keyCode]=true;

}

private function keyUpListener(e:KeyboardEvent):void {
aKeyPress[e.keyCode]=false;
}
}

}
[/cc]

That's all of the code. You can use it as the Main class in a Flex
project or as the document class in a Flash project. It will work
either way.

Beyond:

There are other optimizations that can be used in conjunction with this
code to further control when the screen is updated. One thing I would
add is the ability to not update an object if it hasn't changes. If an
object is simply stationary (x+dx=x and y+dy=y) on a frame, then do
NOTHING to it. Don't erase it, don't re-blit it, just leave it alone.
Another would be to further optimized the animation render by not
updating an object if the same frame of object animation is being
displayed  on this timer tick as was displayed in the previous
timer tick.

27Jan/092

Building A Generalized Flex Game Control Part 4 – TileSheet class

Building A Generalized Flex Game Control Part 4 - TileSheet class

In part 1, I discussed and mused over what parts of the MVC pattern (if
any) to implement in my Flex game control.

In part 2, I dug down into my thoughts on the rendering engine and how I
was unable to get anything displayed on the screen before the pipes
burst at my house and I was forced to continue writing this from a
Hotel (now still).

In part 3, I finally got the code of the rendering engine working (not
completely by any means, but working satisfactorily.

In this forth part we will dissect the TileSheet class. This class is the
basic building block for any game made with this engine. It is used to describe
the tile sheet attributes for a particular png file that can be used to fuel the
blitting engine. It takes a BitmapData object as its first parameter, as well as
the width and height of the entire tile sheet and the width and height of a
single til

Before we go any further, why don't we look at my progress so far with the
reference app that I am using to help test and create the general engine. We'll
then show the entire class code and finally discuss how the reference app makes
use of the TileSheet class.

Reference application so far (not much, but I'll explain what we are seeing
later)

The code for the TileSheet Class

[cc lang="javascript" width="550"]
package com.bitrocket8.display{
import flash.display.BitmapData;

/**
* ...
* @author Jeff Fulton
*/
public class TileSheet{
private var _sourcebitmap:BitmapData;
private var _width:int;
private var _height:int;
private var _tilewidth:int;
private var _tileheight:int;
private var _framesperrow:int;

public function TileSheet(sourcebitmap:BitmapData,width:int, height:int, tilewidth:int, tileheight:int ){
_sourcebitmap = sourcebitmap;
_width = width;
_height = height;
_tileheight = tileheight;
_tilewidth = tilewidth;
_framesperrow = int(_width / _tilewidth);
}

public function getsourcebitmap():BitmapData {
return _sourcebitmap;
}

public function setsourcebitmap(val:BitmapData):void {
_sourcebitmap = val;
}

public function getframesperrow():int {
return _framesperrow;
}

public function getwidth():Number {
return _width;
}

public function setwidth(val:Number):void {
_width = val;
}

public function getheight():Number {
return _height;
}

public function setheight(val:Number):void {
_height = val;
}

public function gettilewidth():Number {
return _tilewidth;
}

public function settilewidth(val:Number):void {
_tilewidth = val;
}

public function gettileheight():Number {
return _tileheight;
}

public function settileheight(val:Number):void {
_tileheight = val;
}
}
}
[/cc]

This is a pretty simple class. It is basically a holder for information about
a BitmapData object plus the information needed to use it as a Tile Sheet for
blitting from. Also, we have some access methods for getting and setting the
attributes. After
completing quite a few games using blitting and tile sheets in AS3, I always
found myself painted into a corner when it came to the tiles the represent the
objects in the game. Too often I needed to change the look of the player for
a few seconds or add an overlay on top. These operations always necessitated custom code that
needed to be shoe-horned inline into the render method of my player object based
on some sort of switch or combination of switches.

To combat that problem I started this Generalized Flex Game Control with the
sole purpose of making it much easier to change the state of an object's look
with out a huge set of in-line game specific code. So, for the above reference
app, I first defined the states for the button GO button in a png.

go button

I decided that the first two frames would alternate when the mouse is "off"
of the button. The 3rd, yellow is the "over" state and the 4th, white is the
"click" state.

This TileSheet is embedded into a Main game control view state called
ScreenTitle. (I will delve into this and all of the other classes in detail in
future installments).   In Flex, the embed looks like this:

[cc lang="javascript" width="550"]
[Embed(source = "../assets/titlescreen.png")]
private var titlescreenPNG:Class;
[/cc]

Later in the ScreenTitle class, we instantiate the TileSheet with this code:

[cc lang="javascript" width="550"]
private var _gobuttonBitmapSource:Bitmap;
private var _gobuttonTS:TileSheet;
_gobuttonBitmapSource = new gobuttonPNG();
_gobuttonTS = new TileSheet(_gobuttonBitmapSource.bitmapData, 400, 50, 100, 50);
[/cc]

Now our TileSheet is ready for use. In the next part of this series we will
explain the BlitContainer class as it currently stands and go into detail on how
the 3 button states are created, updated and rendered.

When you click the [Go] button in the above reference application, you will
be taken to the first game screen in Micro Robot Maze. All of the squares in
this grid are blitted from a single TileSheet also. This grid makes use of some
sub classes of my BlitCanvas class that add in the ability for them to pass
their ID back in a custom click event. In that way, we can easily tell which
square was clicked. Anyway, that is getting ahead of the current lesson. This
was a the direst short tutorial in a series of digestible bites that we will
take while developing this engine over time.

4Dec/081

Tutorial: Using MP3 Files In Flex 3 with Flash Player 9.x

Tutorial: Using MP3 Files InFlex 3 with Flash Player 9.x

This tutorial will explore several different methods to use MP3 files in Flex 3. We will cover simple loading and embedding, as well as strategies for seamlessly looping MP3 files. Some of these concepts can be used for Flash CS2/CS3 AS3 IDE projects where noted.

The Basics

The software used and selections made when encoding an MP3 file for use with Flex can make all the difference when it comes to executing a project. First, when creating a MP3 file for use with Flex, you must ensure that it is encoded with 44kHz or 11kHz sample rate. Flex cannot load in a file encoded at 24kHz or 16kHz. The bits/sec do not matter for compatibility, but the sample rate does. Also, if you are going to be attempting to loop the MP3 file, you will want to find an encoder that DOES NOT add any space at the end of the file. Space at the beginning can be mitigated with offset start playing, but space at the end can be a killer.

We will explore a polling example below that can be used to work with files that contain both leader and follower space, but overall it is best to use an encoder that doesn't add space at the end of the file. The encoder that comes with Sony Acid (for example) doesn't add any "follower" space, just "leader" space.

Simple Loading an MP3 at run time (IDE or Flex)

The first example is the simplest way to load an external sound at run-time. It doesn't wait for the sound to be loaded before it is played, and simply assumes they sound is available.

[cc lang="javascript" width="550"]
package{
import flash.display.Sprite;
import flash.media.*;
import flash.net.URLRequest;
/**
* ...
* @author Jeff Fulton 2008
*/
public class Main extends Sprite{
private var sndChannel:SoundChannel;
private var snd:Sound;
private var sndUrl:URLRequest = new URLRequest("../assets/ambientrock4.mp3");
public function Main() {
trace("loading sound");
snd = new Sound(sndUrl);
trace("playing sound");
sndChannel = snd.play();
}
}
}
[/cc]
In the above example, you must first create a new URLRequest and give it the the location of the file to load. You then need to create Sound object to hold that loaded sound, and lastly, to play the sound, you must add it to a SoundChannel object when calling the Sound.Play() method. That's pretty much it for a simple sound load and play.

Monitoring the load of an MP3 and then Playing the Sound (IDE or Flex)

[cc lang="javascript" width="550"]
package{
import flash.display.Sprite;
import flash.media.*;
import flash.net.URLRequest;
import flash.events.ProgressEvent;
import flash.events.IOErrorEvent;

/**
* ...
* @author Jeff Fulton 2008
*/
public class Main extends Sprite{
private var sndChannel:SoundChannel;
private var snd:Sound;
private var sndUrl:URLRequest = new URLRequest("../assets/ambientrock4.mp3");

public function Main() {
trace("loading sound");
snd = new Sound(sndUrl);
snd.addEventListener(IOErrorEvent.IO_ERROR, snderrorHandler);
snd.addEventListener(ProgressEvent.PROGRESS, sndprogressHandler);
}

private function sndprogressHandler(e:ProgressEvent):void {
trace("loading sound...");
if (e.bytesLoaded >= e.bytesTotal) {
playSound();
}
}

private function snderrorHandler(e:IOErrorEvent):void {
trace("sound load error " + e.text);
}

private function playSound():void {
sndChannel = snd.play();
}
}
}
[/cc]

In the above example code we have added 2 event listeners to aid in monitoring the load progress of the file and have also added a function called playSound():void that will be called when the file has been completely loaded in. There is no onComplete event that fires when the sound has been loaded, so we must check the sndProgressHandler
method until the sound has been loaded, and then we call the playSound():void method. The snderrorHandler method would be called if there was some type of security access violation or a file not found error.

Simple Embedding of an MP3 at compile time (Flex Only)

[cc lang="javascript" width="550"]
package{

import flash.display.Sprite;
import flash.media.*;

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

//*** 1 Basic
trace("attempting straing sound embed");
[Embed(source="../assets/ambientrock4.mp3")]
[Bindable]

public var sndCls:Class;
trace("finished straight sound embed");
public var snd:Sound = new sndCls() as Sound;
public var sndChannel:SoundChannel;

public function Main(){
//*** 1 basic
//basic sound play with offset
sndChannel = snd.play();
}
}
}
[/cc]

A Simple embed of a sound in a Flex app is quite easy (you cannot embed in a CS3 file). It is very similar to the simple load of an MP3, but even easier. Once the [Embed] is called, you must declare a class for the file directly underneath. You use this class name when you instantiate your Sound object and use the "as" construct to cast the generic loaded asset as a Sound. You then can play the sound in the exact same manner you would if it was loaded in, by assigning it to a sound channel.

Simple Seamless Looping the play of a loaded or embedded .mp3 file (IDE or Flex)

You can easily loop the play of an MP3 by calling play() and passing in
an offset and a number of loops. The offset is critically important
here, because if you were able to encode your MP3 file with an encoder
that doesn't put and "follower" space at the end of the file, you will
be able to simply play with the offset number of milliseconds until the
file loops seamlessly. For the example above, I changed the play() to
look like this:

[cc lang="javascript" width="550"]sndChannel = snd.play(956,5);[/cc]

That tells the SoundChannel to play the loop 5 times and always start 956 milliseconds into the file on every loop.

Advanced Seamless Looping of a MP3 file- for files that have space at the end of the file, as well as the beginning.
(IDE or Flex)

I attempted many different methods of seamlessly looping the play of a MP3 file in Flex, but none of them worked out any better than a version I did in AS2 a while back. My advanced attempt was to Embed the MP3 with a Mime Type setting of "mimeType="application/octet-stream". This will basically load the file into a ByteArray instance. I then copied the bytes after the leader blank space to another ByteArray and attempted to cast the new ByteArray as a sound. That will not work because when a sound is embedded as a MP3 file, it is converted to a type of file that can be used by the Flash Player, and is no longer a straight set of Bytes that represent an MP3. Since the cast would not work, I got an run-time exception and the sound never played.

Why then did Adobe not strip the leader and follower space off of the file when it converted to an internal type? I have no idea. I searched far and wide to find a solution, but it was fruitless. There were may discussions on the topic, but no answers. I even found a bug in the online Flex bug database that offered Adobe a brilliant solution - add an option into the play() function that allows the user to set a number of milliseconds as an
ending offset to skip before playing the sound again. I was able to see some hope in the Flash Player 10 spec, but since this entry is focused on Flex 3 and Flash Player 9, that will have to wait for another  research effort.

How to seamlessly (but not painlessly) loop an MP3 file with no help for Adobe.

Is there an answer that will help skip both the "leader" and "follower" space in an MP3 file in Flex 3, Flash Player 9? The answer is yes, but it makes use of a polling concept that will add some slight overhead to your application as you will need to constantly check the location of the MP3 play head and force it to start over when it passes the last real byte of music, before the follower space. I will present it here as a solution that you should use wisely. It is best added to a main loop single timer and should not be used with a
separate timer for each sound.

That being said, here is the code:

[cc lang="javascript" width="550"]

package{

import flash.display.Sprite;
import flash.media.*;
import flash.events.TimerEvent;
import flash.utils.Timer;

/**
* ...
* @author Jeff Fulton 2008
*/
public class Main extends Sprite{
//*** 1 Basic
trace("attempting straing sound embed");
[Embed(source="../assets/ambientrock4.mp3")]
[Bindable]
public var sndCls:Class;
trace("finished straight sound embed");
public var snd:Sound = new sndCls() as Sound;
public var sndChannel:SoundChannel;
public var leader:int = 1000;
public var follower:int = 400;
public var placeToStop:int=snd.length-follower;
public var gameTimer:Timer;

public function Main(){
//*** 1 basic
//basic sound play with offset
sndChannel = snd.play(leader);
gameTimer=new Timer(1);
gameTimer.addEventListener(TimerEvent.TIMER, runGame);
gameTimer.start();

//sndChannel = snd.play();
}

public function runGame(e:Event):void {
trace(int(sndChannel.position) +"/" + placeToStop);
if (int(sndChannel.position) >= placeToStop) {
trace("hit position")
sndChannel.stop();
sndChannel = snd.play(leader);
}
}
}
}
[/cc]

The basic theory behind this approach (hack) is to play the sound one time, starting at an offset that is passed the leader space one time without setting any looping properties. While the sound is playing, during a timer or EnterFrame event, you poll repeatedly to see if the current position of the sound playing is near your offset for the ending before follower space. If that condition is true,  the sound is stopped, and started again at the beginning + the starting offset (leader).

The leader variable above is set to 1000 and the follower is set to 400. The sound will start playing 1000 milliseconds in and stop 400 milliseconds from the end.  The placeToStop variable is set to be the length of the MP3 in milliseconds minus the follower value.  You will need to play with these numbers as they will be wildly different with various MP3 codecs and sample rates.
Also, in AS2, the sound.duration attribute (not in as3) was exactly correct and could easily be polled. In AS3, the sndChannel.length seems to be longer than the actual max position of the play head for the sound file. I had to set the follower value to be much greater than I thought for it to work properly.  As the above example plays, I can see the traces of the current position never actually get to the exact length that is reported in the length
attribute.

While this approach works (the above, when used with that particular sample will seamlessly loop the file), it does take some trial and error. Also, you will really want to use this type of polling inside your main loop and not as a separate timer for every looping sound.

One other thing to note is that the position parameter of the SoundChannel object is a real number, but I changed it to an int for this example. For this particular MP3, it worked best as an int, but a real number might be the choice for an MP3 that is encoded with a different codec.

For further reading on the theory behind polling for the end of the MP3, take a look at my
AS2 article that was the basis for this version
.

22Sep/080

8bitrocket Diatribe: Tile-based BitmapData Fine Scrolling

8bitrocket Diatribe: Tile-based BitmapData Fine Scrolling

I'm currently working on a 4 way scrolling car driving game, somewhat like Atari 800/C64 Rally Speedway or Mattel Intellivision's Auto Racing. I have "borrowed" (for testing purposes only" the graphics from a very early Atari/Kee Games coin-op called Super Bug, which in itself was the first game of this kind (4-way scrolling, top-down driving game). It turns out that I have never made a 4-way scrolling game before. I have made horizontal scrolling racing games (here and here) (ages ago) with a GAS tile method (goto and stop) , but never a fully blit scrolling engine.

So, I spent the last few days researching the subject in various books, online articles and personal references (thanks, Cutler). I found that the the most common method for creating a tile-based BitmapData world is to create a HUGE world BitmapData object composed of your entire tile map (or as much would fit in the max BitmapData size). This world would NOT be rendered to the screen, but you would render a "camera" or "view" of that world by moving the copyPixels Rect location to the proper x,y coords and copying a screen-sized portion of the world to the view. My buddy Chris had made a game like this, and it sounded like an interesting, if restricting way to create a scrolling world. Chris had also created a very new game using a more complex method - render a screen sized portion of tiles (plus a bordering buffer tile all around) and use BitmapData.scroll() to move the output window to the proper place in the buffer and copyPixels.

Starting with Chris's second idea, but absolutely no reference code, I spend the last week fumbling my way through my own lame interpretation that never quite worked very well. The best I came up with on my own worked great in the Right and Down directions, but going up and left the buffer would not fill in the top or left hand column until an entire tile was moved, creating a gutter effect. That version wasn't even using the BitmapData.scroll() method, it was just creating a buffer with the extra tile around the entire world, and moving an offset when an arrow key was pressed. The offset would tell the final copyPixels operation where to begin the copy to the final output screen. I don't know why I am going into detail because it created a gutter effect anyway, but my point is that I was never able to get the scroll() version to work. I tried, but scrolling the BitmpaData object created some awesomely strange visual effects that I could probably sell to Jeff Minter, but couldn't use in my game.

Anyway, armed with a slightly working version, and the knowledge that Chris imparted on me (even though I was too obtuse to figure out exactly what he had done), I embarked on my new version tonight and I think I might finally have something. It isn't optimized, and I am sure (hopefully) someone will email me with a list of things I can do better to make it more efficient, but I think I finally have one that works.

I have to thank Jonathan S. Harbour for his help on this. I have two of his books, one on Dark Basic, and one on 2d Windows game programming. I found an invaluable chapter in his Dark Basic book on doing exactly what I wanted. When I couldn't get my jumble of spaghetti to scroll properly, I read and re-read his chapter about 5 times and then took his basic points and ported them to AS3. It doesn't implement the BitmapData.scroll(), but uses an over scan like buffer to copy from. This creates TWO copy pixel operations: one to get each tile to the buffer and another to copy the correct portion of the buffer to the output. I have read that the scroll() method is basically doing another copyPixels, so I might not save anything even if I do figure out that method.

The (not complete in the least) pseudo-code (plus some real code) is here: (the real code is a jumbled mess with a HUGE set of embedded XML that looks like crap.
1. Create a canvas BitmapData object the size of the output window
2. Create a buffer BitmapData object the size of the output widow + 2 * the tileWidth for the width.
and + 2 * the tileHeight for the height
3. When a key is pressed, increment or decrement the xoffset or yoffest by 1 (depending on the direction pressed)
4.(here is the actual rendering code)

[cc lang="javascript" width="550"]
//calculate starting tile position
var tilex:int=int(viewXOffset/mapTileWidth);
var tiley:int=int(viewYOffset/mapTileHeight);
var columns:int=int(viewWidth/mapTileWidth);
var rows:int=int(viewHeight/mapTileHeight);
var rowCtr:int=0;
var colCtr:int=0;
var tileNum:int;

for (rowCtr=0;rowCtr<=rows;rowCtr++) {
for (colCtr=0;colCtr<=columns;colCtr++) {
tileNum=aWorld[rowCtr+tiley][colCtr+tilex];
tilePoint.x=colCtr*mapTileWidth;
tilePoint.y=rowCtr*mapTileHeight;
tileRect.x = int((tileNum % sprites16x16_perRow))*mapTileWidth;
tileRect.y = int((tileNum / sprites16x16_perRow))*mapTileHeight;
bufferBD.copyPixels(sprites16x16,tileRect,tilePoint);
}

}
bufferRect.x=viewXOffset % mapTileWidth;
bufferRect.y=viewYOffset % mapTileHeight;
canvasBD.copyPixels(bufferBD,bufferRect,bufferPoint);
[/cc]

5. The basics are that you first find the tiles you need to display and copy them to the buffer,
then you figure out what part of the buffer needs to be painted to the output canvas.

If this was a full tutorial, I would spend more time going over the code line by line. I am still working out the kinks, but here is the current version.
It starts in the upper left-hand portion of the map, and there are only 4 different tile types (straight from Super Bug). It scrolls slowly because the increment is 1 pixel at a time.

No optimizations have been implemented. This is pretty much straight out of my testing ground. Nothing revolutionary, but I'm happy to finally get something to work properly.

Here is the working version (arrow keys scroll 1 pixel at at time):

(note: as of 9/22/2008, scrolling in the max y direction causes an unhandled exception...I guess I need to handle my exceptions better! The x direction works fine, so I must have fudged something with my fat fingers when doing the y)

15Sep/082

8bitrocket Diatribe: Drawing Vector Primitives into a BitmapData Object in AS2 and AS3.

8bitrocket Diatribe: Drawing Vector Primitives into a BitmapData Object in AS2 and AS3.

That doesn't sound too difficult, and it really isn't. I received an email question this weekend from a game developer who was stumped at how to accomplish this in AS2. I knew it was pretty easy to do in AS3 (I think Moock has an example in his latest book), but I had never tried it in AS2. Anyway, it turned out to be pretty easy, with one minor exception that I have yet had time to figure out. The below examples basically draw a vector primitive square on the screen and then take a BitmapData snapshot and re-draw that snapshot to the screen from the BitmapData. The square is a simple 20x20 at 0x,0y and then takes the snapshot and paints the BitmapData version at 0x, 50y. When I made a BitmapData object the exact size, 20 x 20, the right side and bottom are clipped in the copy. So, I just made the BitmapData object 21x21 and it looks fine. I hate crap like that though, but don't have time tonight to get it to work without the 1 pixel size extension on each axis.

Anyway, since I don't have to to make this into a full tutorial, here is the code and the example of the AS2 version: I just dropped the code on the main timeline.

AS2 Version:

[cc lang="javascript" width="550"]
//AS2
import flash.display.BitmapData;
import flash.geom.*;

this.createEmptyMovieClip("drawCanvas",this.getNextHighestDepth());
drawCanvas.lineStyle(1,0xFF00FF);
drawCanvas.lineTo(20,0);
drawCanvas.lineTo(20,20);
drawCanvas.lineTo(0,20);
drawCanvas.lineTo(0,0);

var tempBitmap:BitmapData = new BitmapData(21,21);
tempBitmap.draw(drawCanvas,new Matrix());

this.createEmptyMovieClip("bitmapHolder",this.getNextHighestDepth());
bitmapHolder._x=50;
bitmapHolder.attachBitmap(tempBitmap,bitmapHolder.getNextHighestDepth());
[/cc]

Nothing tricky going on here. Just create a MovieClip to use as a drawing canvas for the vector shape. Draw on it in and draw() that into a BitmapData object.
Here is the working example:

Here is the AS3 version:

[cc lang="javascript" width="550"]
//AS3
import flash.display.*;
import flash.geom.*;

var drawCanvas:Shape=new Shape();
drawCanvas.graphics.lineStyle(1,0xFF00FF);
drawCanvas.graphics.lineTo(20,0);
drawCanvas.graphics.lineTo(20,20);
drawCanvas.graphics.lineTo(0,20);
drawCanvas.graphics.lineTo(0,0);
addChild(drawCanvas);

var tempBitmap:BitmapData = new BitmapData(21,21);
tempBitmap.draw(drawCanvas,new Matrix());
var bitmapHolder:Bitmap=new Bitmap(tempBitmap);
bitmapHolder.x=50;
addChild(bitmapHolder);
[/cc]

This is even easier than the AS2 version because AS3 contains an actual Shape() class to use instead of a MovieClip for a canvas. Just draw into a Shape instance and the draw() in into a BitmapData object.

Here is the working example:

30Jul/082

Tutorial Update: Basic Blit with Transparency

Tutorial Update: Basic Blit with Transparency

Super cool 8bitrocket.com reader, MR. K, asked a very interesting question today. He asked if this tutorial included transparency because both the screen background and the helicopter background are black. Some how I completely missed that point when I created the original tutorial. The answer is that of course blitting can be done against a complex background with transparency, but the sprite sheet I used was didn't have any transparency, and my background was just a black square. Opps!

Sure enough, the original tile sheet was a png file, but it had a solid non-transparent background. So, I opened up the tile sheet in Fireworks, selected the background with the selection tool and deleted it out. I saved it off as a png file and re-imported it into Flash. The new file looks like this:

Since our site has a dark gray background, you can tell right away the difference between that file and this one from the original tutorial.

The difference is HUGE. As the original file created a set of sprite tiles with a big black square around them.

I also created a 400x400 background tile to use as a background:

When they are put together, they look like this:

Very few code changes were needed, I just referenced a library item as the background instead of instantiating a black square. The tile sheet in my code was already set to use transparency, so no other changes were needed.

All of the new source files are here