8bitrocket.com
30Oct/0711

Actionscript 3: Tutorial – BitmapData rotation with a matrix

The bitmapData class has been a revelation for advanced Flash game programmers. With it, one can replicate the sprite sheet magic that 8-bit game wizards produced on machines such as the Atari 800, C=64 and Nintendo NES. I have covered the use of sprite sheets and BitmapData in a previous tutorial: Flash CS3: Actionscript 3 (AS3) Game Primer #1: Tile Maps, XML, and bitmapData. In this lesson, we will explore a technique for manipulating the Bitmap Data object through the use of some standard matrix operations.

I have been building a new game called Pumpkin Man. It is an AS3 tile-based, Pacman variant. I have been putting special emphasis on the game display engine to ensure future that AS3 games I create can make use of it. I have created an engine that uses one display object (a sprite) to display the entire game. This sprite contains one Bitmap object, and one BitmapData object (inside the Bitmap). The tiles, the background, the power ups and and all game characters are redrawn each frame into this one Bitmap Data object. This eliminates the need for the rendering engine to constantly update the math necessary to move vectors, and the movie has just one redraw region. The result (so far) is a fast, slick display engine. The problem is that many of the easy things you can do with MovieClips, such as rotation around the center point, scaling, etc are not as easy to accomplish because the game characters are not drawn within their own display objects. What follows ia an example of how I solved one such problem: Rotation around a center point.

Rotating a game character around the mid point.
When Pumpkin Man, the main character in the game dies, I wanted him to spin around his mid point and quickly scale down. It gives the slight impression of being flushed down the toilet. I could have easily done this with standard display object manipulations. Here are the steps I would have taken if the game character was inside a Sprite:

1. Make sure the Bitmap holder for the character was centered at -1/2 width for x and -1/2 height for the y value. This x and y position is relative to the local coordinates of the Sprite holder for the Bitmap. This puts the Bitmap holder's center point at the origin of the Sprite.
2. Rotate the Sprite and scale in an EnterFrame or timer operation to create the illusion of rotation and scaling down.

But, in my case, I only had the actual BitmapData to use, and no individual Sprite and Bitmap holders to manipulate. I didn't create a rotated sprite sheet version of each rotation frame - which would have made this easier, but also eats up valuable bandwidth and ram. I had one frame on BitmapData to use. The solution is to use the Matrix class to transform the BitmapData on each frame update. The Matrix.rotate() method takes an angle in radians as its one parameter. Since a BitmapData object's origin point is set to 0,0 always, and can't be changed though a property update method, you must also use a Matrix.translate() function call (actually) two of them to create the illusion of rotation around the character's center point.

First we need to create a generic main document class to hold our code.

 

[cc lang="javascript" width="550"]/**
* ...
* @author Jeff Fulton
* @version 0.1
*/

package {

import flash.geom.*;
import flash.display.BitmapData;
import flash.display.MovieClip;
import flash.events.*;

public class Main extends MovieClip {

public function Main() {
trace("main");
}

} // end class

} // end package
[/cc]

This is the basic structure for the class. It is going to be the document class for our .fla, so you can set "Main" as the Document class in the document properties window
Imports:
1. import flash.geom.*; - to do the matrix transformations and create Point and Rectangle instances.
2. import flash.display.BitmapData; - for the BitmapData object that we are going to use to display our character.
3. import flash.display.MovieClip; - MovieClip (or Sprite) because this is the Document class of a MovieClip;
4. import flash.events.*; - For our EnterFrame event.

I have placed a 32x32 Bitmap in the library that will be out character to rotate and scale. Its linkage name is set to "character". Our next step will be to create the necessary variables to store and manipulate this character on the screen. The following code updates the previous code and ads some new variables and functions:

 

[cc lang="javascript" width="550"]/**
* ...
* @author Jeff Fulton
* @version 0.1
*/

package {

import flash.geom.*;
import flash.display.BitmapData;
import flash.display.MovieClip;
import flash.display.Bitmap;
import flash.events.*;

public class Main extends MovieClip {
private var screenBD:BitmapData;
private var backgroundBD:BitmapData;
private var screenB:Bitmap;
private var charObj:Object=new Object();

public function Main() {
createScreen();
createCharacter();
renderCharacter();
}

private function createScreen():void {
backgroundBD = new BitmapData(300,300,false,0x000000);

screenBD=new BitmapData(300,300,false,0x000000);
screenB=new Bitmap(screenBD);
addChild(screenB);
}

private function createCharacter():void {
charObj.sourceBD=new character(32,32);
charObj.displayBD=new BitmapData(32,32,true,0xffffffff);
charObj.displayBD.copyPixels(charObj.sourceBD,new Rectangle(0,0,32,32),new Point(0,0));
charObj.x=50;
charObj.y=50;
charObj.rect=new Rectangle(0,0,32,32)
charObj.point=new Point(charObj.x,charObj.y);
}

private function renderCharacter() {
screenBD.copyPixels(charObj.displayBD,charObj.rect, charObj.point);
}

} // end class

} // end package
[/cc]

We now have declared these variables:
1. private var screenBD:BitmapData; - This will be the bitmap data holder for our screen. This is a container for all of the objects we will add to the screen. Since it is a BitmapData object, we must either draw() or copyPixels() from displayObjects such as BitmapData to update this screen.
2. private var backgroundBD:BitmapData; - This will be used as an "eraser" between frame draws. We need to ensure there is no ghosting of pixels or trails left behind as we rotate and scale.
3. private var screenB:Bitmap; -This is the container to be added to the main movie. It will contain the screenBD.
4. private var charObj:Object=new Object(); - This is a generic object that will hold an instance of our character class. If this was a full blown game, we would make this a real class, but in this instance, we will make use of the simple generic Object class. If you have ever programmed in C, this is much like a struct. It will hold structured data about our object, but will not have and accessor or other methods (because it can't).

Next we have our main function. This currently just calls more functions to set up the screen, character, and finally render the character.

The createScreen() method simply creates a 300 x 300 Bitmapdata object with transparency set to false, and black as it's fill color. We also call the addChild() of our main MovieClip to display it on the screen. It also created the backgroundBD that will be used as an eraser. This is because once we start drawing on the current screenBD, we will be destroying all of the current black pixels and replacing them with colored pixels. These back pixels will never come back unless we redraw them. The backgroundBD is used for that purpose.

The createCharacter() is a little more involved.
1. charObj.sourceBD=new character(32,32); - This creates a sourceBD (Bitmapdata) instance from the character in the library. We must pass the size of the source image into the the constructor for a library object with flash.display.BitmapData set as the Base Class. This makes it a simple task to pull items from the library and create instances of them, especially BitmapData objects.
2. charObj.displayBD.copyPixels(charObj.sourceBD,new Rectangle(0,0,32,32),new Point(0,0)); - We copy all of the pixels from the sourceBD to the displayBD. We do this because the matrix operations we will do are "destructive", meaning that they change the actual BitmapData of the object and cannot be easily reversed. We will do these operations on the "cloned" copy called displayBD and leave the sourceBD intact. We need to use this method because if we just assigned one instance to the other with the "=" operator we would have two instances that are pointers to or references of the exact same data. Matrix operation changes would then be destructive to both and not just the displayBD. Believe me, I learned this the hard way.
3. Next we set and x and y property of the object both to 50. This is simply the upper left-hand corner point where the character will be drawn into the screenBD.
4. The rect is an instance of the Rectangle class and it holds the clip area of the BitmapData to copy to the screenBD. Since we want the entire character to be copied to the screenBD, we set the start point at 0,0, and the end point at 32,32.
5. The point variable constructs an actual Point class instance from the x and y values created above.

Then renderCharacter() method is currently deceptively simple:
screenBD.copyPixels(charObj.displayBD,charObj.rect, charObj.point); It is pretty powerful for one line. Basically this tells the screenBD to copy all of the pixels from the charObj's displayBD instance to itself. It uses the rect as the clip area to copy and the point as the location on the screen to place the pixels it copies.

Next we add in code to loop and rotate the character.

 

[cc lang="javascript" width="550"]/**
* ...
* @author Jeff Fulton
* @version 0.1
*/

package {

import flash.geom.*;
import flash.display.BitmapData;
import flash.display.MovieClip;
import flash.display.Bitmap;
import flash.events.*;

public class Main extends MovieClip {
private var screenBD:BitmapData;
private var backgroundBD:BitmapData;
private var screenB:Bitmap;
private var charObj:Object=new Object();

public function Main() {
createScreen();
createCharacter();
addEventListener(Event.ENTER_FRAME, run);
}

private function run(e:Event):void {
//trace ("running");
updateCharacter();
renderBackground();
renderCharacter();
}

private function createScreen():void {
backgroundBD = new BitmapData(300,300,false,0x000000);
screenBD=new BitmapData(300,300,false,0x000000);
screenB=new Bitmap(screenBD);
addChild(screenB);
}

private function createCharacter():void {
charObj.sourceBD=new character(32,32);
charObj.displayBD=new BitmapData(32,32,true,0xffffffff);
charObj.displayBD.copyPixels(charObj.sourceBD,new Rectangle(0,0,32,32),new Point(0,0));
charObj.x=50;
charObj.y=50;
charObj.rect=new Rectangle(0,0,32,32)
charObj.point=new Point(charObj.x,charObj.y);
charObj.rotation=0;
}

private function updateCharacter():void {
charObj.rotation+=18;
if (charObj.rotation <361) {
var degrees:int=charObj.rotation;
var angle_in_radians = Math.PI * 2 * (charObj.rotation / 360);
var rotationMatrix:Matrix = new Matrix();
rotationMatrix.translate(-16,-16);
rotationMatrix.rotate(angle_in_radians);
rotationMatrix.translate(16,16);
var matrixImage:BitmapData = new BitmapData(32, 32, true, 0x00000000);
matrixImage.draw(charObj.sourceBD, rotationMatrix);
charObj.displayBD=matrixImage;

}
}

private function renderBackground():void{
//trace("render background");
screenBD.copyPixels(backgroundBD,new Rectangle(0,0,300,300), new Point(0,0));
}

private function renderCharacter() {
//trace("render charcter");
screenBD.copyPixels(charObj.displayBD,charObj.rect,charObj.point);
}

} // end class

} // end package
[/cc]

We have added in a lot of code here. First I will explain all of it, then you will see a demo of it in action and be able to download a .fla with a working example.

In the Main() method, we added this line: addEventListener(Event.ENTER_FRAME, run); We now have an event that will fire off each frame and will call our run() function;

The run(e:event) function repeatedly calls these methods: updateCharacter(); renderBackground(); renderCharacter(); We already have discussed the unchanged renderCharacter() function. The other two will discussed in detail.

The updateCharacter() method is used to update the BitmapData for the character before the renderCharacter() method is called. What we want to do in this example is rotate the character 18 degrees on each frame and have the rotation stop after on revolution.
charObj.rotation+=18; This simply adds 18 to the number we are using to hold the rotational degree for the character.
if (charObj.rotation <361) { - This starts out iteration and will not continue once the degree value has reached 360;
var degrees:int=charObj.rotation; Here we create an int to hold the current rotation value. I do this just in case there is a data type problem with the generic object. By creating an int, I am sure the data held in the variable will be the correct type);
var angle_in_radians = Math.PI * 2 * (charObj.rotation / 360); - We need to translate degrees into radians because the Matrix class uses radians in angle calculations.
var rotationMatrix:Matrix = new Matrix(); - We simply create a new Matrix for the rotation.
rotationMatrix.translate(-16,-16); - Here we use the translate method of the Matrix class to move the matrix to -16, -16. This is equivalent to setting it's origin point to -16, -16. We are using 16 here because the width and height of out character is 32.
rotationMatrix.rotate(angle_in_radians); - We call the rotate() method of the matrix and pass in the radians of our degree angle. This will cause the rotation to be calculated on the current translated position.
rotationMatrix.translate(16,16); - We move the matrix back to 0,0 be adding 16 to the x and the y (tx and ty in the matrix). This works because all Matrix operations are additive. We could never just move it to a position without knowing its current position. Since it started at 0,0, was moved to -16,16, we need t0 just add 16 to the tx and 16 to the ty of the matrix to put it back at origin 0,0.
var matrixImage:BitmapData = new BitmapData(32, 32, true, 0x00000000); - We create a new Bitmapdata object to hold the result of our matrix operation on the charObj.sourceBD operation.
matrixImage.draw(charObj.sourceBD, rotationMatrix, null, null, null, true);- We draw into our clean, new matrixImage the sourceBD with the matrix applied. We set the final option of Pixel Smoothing to true so our rotated bitmap looks as close to a rotated vector as possible.
charObj.displayBD=matrixImage; - Finally, we have our displayBD reference the new matrixImage.

We have one last function called renderBackgroud(): Its basic job is to wipe clean the screenBD by copying the background to it on every frame before the character is drawn:
screenBD.copyPixels(backgroundBD,new Rectangle(0,0,300,300), new Point(0,0));

bd_rotate.zip - Example .fla

19Oct/070

Hotwheels.com Spin City Game Goes Live!

titl250.jpg

Finally, my newest game for Hotwheels.com has gone live!  "Hot Wheels Spin City Drive-Thru Dilemma" is my casual game "sequel" to "Hot Wheels Accelracers Track Mod", released in 2005.   It is not really a "sequel" though, as it's game-play is based on the "Diner Dash" model, not on the "Pipe Dream/Rocket Mania" model of Track Mod.  However, the two share a lot of the same code-base, and they are both "puzzle" style games, and the both are outside the normal style of games that we put on hotwheels.com.

I did the game design, progamming, and created the music for this game.  The graphics were created by Ben Liska from my basic graphical skeleton.   I've been playing this game for so long, I can no longer tell how easy or hard it might be.  I spent about 3 weeks play-balaancing the game, and the testers told me it was just right, so I believe them. However, my gut feeling is that it could probably use a bit more tweaking.

I will follow-up this post in the next few days with a technical discussion of the game development.

Anyhow, please try the game and tell me what you think.

Thanks,

Steve Fulton

info@8bitrocket.com

 

 

15Oct/070

Spooky Jack-o-Lantern Designer (2007)

Design and print "spooky" jack-o-lanterns in this easy and fun activity for kids.

Filed under: Games, Kids No Comments
15Oct/070

MochiAds Goes Public!

It looks like MochiAds has gone public today!  Check themout over at http://www.mochiads.com.   Also, they something new called "Customoized Ads" that looks very interesting...We'll report more later when we know the details.

14Oct/071

Implementing MochiAds in a Flash AS3 Class

MochiAds are still in beta, but we here at 8bitrocket are firm supporters of this system. Mochi seems to be one of the few systems that is looking out for the independent developer. You can read more in Steve's entry from a few weeks back.

I am designing a new game called Pumpkin Man. It is based on Pac man. I could have simply made a Halloween Based Pacman clone hand been done with it in a week or some, but I took the opportunity to spend some time creating a new game loop engine in AS3. The engine that includes a State Machine Gameloop (a little like Moock's GameConsole if you have his new book) and a generic GamePlay loop for running a game. I am also creating a new rendering engine that uses all bitmaps and no Movieclips. Actually, it uses all bitmapData and one MovieClip to attach all visual changes to much like an Atari ST or Amiga "bit block transfer" or "blitter" as Atari called the chip that finally made it into the STE platform well after the Amiga had taken the ST's crown as the worlds best computer gaming machine (or at least Europe's Best). It you are interested in game design at all, you can check out the first entry in the Pacman game redesign called Launching Pad: Redesigning a Retro Classic #1: Pacman. Designing a Maze Chase game is not as easy as it first appears, especially is you don't want to take shortcuts like many similar Flash Games have. I will be continuing this series this week with more entries on game design, State machines, grid movement, and AI.

In designing the generic GameLoop, which takes care of things like pre-loading, showing Mochiads, title, instruction, high score screens,etc, I found the need to implement the Mochiad platform in AS3. While this is not too difficult, it might be confusing to some, and as I had to hunt an peck around a little to do a full Class based implementation, I figured I would share my experience with others in case anyone else found a need for it.

You will first need to visit MochiAds.com and sign up as a beta tester, or if it is in production when you read this, sign up as a game creator. You will be given access to the MochiAd code for as1, as2, and as3. While all three of these can be easily added to the timeline directly, what if, like me, you have a main document class that controls your timeline. In this case, you must implement it inside the document class because mixing a document class (especially a State Machine) and timeline based code will be confusing at best and disastrous at worst. So, without further delay, here are the basic steps to implementing Mochiads in AS3.

Step 1: Make sure the Mochiad.as file is in the same root folder as your .fla or in the same class path as your document class. I am going to refer to the document class as DocumentClass.as from now on.

Step2: Your DocuementClass.as must be declared Dynamic. This is because the Mochiad Class needs to write to the main Document class and create some variables. It will look like this:
dynamic public class DocumentClass extends MovieClip {

Step3: In your code you will have some place where you want to show an ad. In this example I am using the "showPreloaderAd" because I want to show the ad before the game loads. The call will look something like this:
MochiAd.showPreloaderAd({clip:this, id:"gameifrommochi", res:"400x400", ad_finished:this.adFinished});

Let's break this down into pieces because it is the crux of the implementation.
1. the clip parameter is a reference to the movieClip that will be the parent for the ad. Since we are in the DocumentClass, and it extends MovieClip and is Dynamic, it is the perfect home for the ad, so we pass in "this".
2.Tthe id parameter is the text string that Mochi gives you when you register a game with them.
3. res is the resolution of the game .fla.
4. The ad_finished let's you specify a function to call when the ad is done showing. This is the most important part because in a timeline based implementation, Mochi can simply jump to the next frame, but in a DocumentClass based implementation that might be difficult or impossible, so you need to specify this callback function to move on to the next "STATE" on your game.

Here is an example of the function taken directly from my DocumentClass.as file:

[cc lang="javascript" width="550"]
function adFinished():void {
trace("ad finished");
mochiadOn=false;
systemState=STATE_SYSTEM_LOADER;
}
[/cc]

Basically once the ad is finished, I have my State Machine jump to the next state. Obviously, your implementation will be different, but as you can see it is pretty simple to accomplish. Your implementation might broadcast an event to tell the next game phase to play (show instructions, etc), or like me, you might set some variables and wait for the State Machine to loop back through and jump to the next state. Just keep in mind that the DocumentClass.as must be dynamic and the call back must be used to call a function when the ad is finished.

 

Filed under: Tutorials 1 Comment