Hey guys. I’m going to try a new thing with this blog of mine. Instead of just rambling on about my own game development news and whatnot, I figure I’ll also throw in a tutorial post every now and then. Sound good? Good.
First, a little background. This concept of using bitmaps instead of the standard MovieClips that everyone is used to came to way back when I first began development on Don’t Panic. I had run into a strange problem that I hadn’t encountered before in either Weapons on Wheels, Oil Spill Escape, or Christmas Defense. The problem had to do with the game lagging up very badly when there were more than about 4 or 5 enemies on the screen.
After some help from the FlashGameLicense.com forums, I realized the problem was the enemy MovieClips – the vector graphics for the enemies were just too detailed and thus the game was having a hard time rendering them every frame. (Vector graphics are essentially just a bunch of math equations that the CPU has to process in order to draw the image on the screen – you could imagine how a bunch of detailed vector graphics could take a toll on your processor.)
Anyway, this is how I came across this method of handling my game art assets. Ever since I’ve learned about it, I’ve used it for Don’t Panic, the late Hero Dude, and my current WIP – Defective. This method isn’t exactly blitting (which is slightly more advanced and is another tutorial all together), but sort of a middle-ground between it and the traditional usage of MovieClips. So, let’s get started, shall we?
The Classes
Alright, now, from here on out, I’m going to be showing you how I utilize this method. Take from it what you will and use it however you want/need.
To begin, I create two classes:
- BMPLoader.as – This class is what “loads” the BitmapData information from your MovieClips and stores it in memory. (It essentially takes a MovieClip and outputs a Bitmap of it.)
- BMPObject.as – This is what you will extend your game objects as. For example:
[cc lang=”actionscript3″ escaped=”true”]
package
{
public class Player extends BMPObject
{
}
}
[/cc]
So far, so good. Now let’s get into what each class does. Let’s begin with BMPLoader.
BMPLoader.as
Alright, this is the class where all of the magic happens, it’s not very difficult once you just look at what’s going on. (I’m just going to throw a disclaimer here that from this point on there may very well be optimizations and better ways to do what I’m trying to accomplish. Feel free to experiment and change things to your needs or even comment on this post telling me what to change.)
Here’s the entire class, commented completely to help you understand it better.
[cc lang=”actionscript3″ escaped=”true”]
package
{
// Imports for this class.
// Nothing more to say really ;).
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.MovieClip;
import flash.geom.Rectangle;
public class BMPLoader
{
// This is an important part of this class.
// Here is where I declare a Vector of type
// BitmapData for EACH MovieClip that I will
// be transforming into a Bitmap. So I’d have
// one for Bullet, Coin, Enemy, etc. Here I
// have one for the Player graphic, just for
// this example. Also important – note how the
// Vector is PUBLIC and not private.
public var vecPlayer:Vector.<BitmapData>;
// This class also needs a DisplayObject which
// will act as the reference to the coordinate
// space to use. This may sound confusing, just
// use a reference to the Level, World, Stage –
// whatever the “global parent” is.
private var levelRef:DisplayObject;
// The constructor doesn’t do anything other than
// assign the “global parent” variable a value. This
// is important to do!
public function BMPLoader(_levelRef:DisplayObject)
{
levelRef = _levelRef;
}
// This destroy() function is more of a personal thing
// I add to each of my classes in order to clean up and
// help for garbage disposal and the freeing of memory.
// Think of it as the opposite of the constructor – the
// destructor, it cleans up the objects and prepares it
// for deletion. Here all I do is null all of the Vectors
// I’ve created as well as my “global parent” reference.
public function destroy():void
{
vecPlayer = null;
levelRef = null;
}
// The load() function – here’s where all of the magic happens.
// This functions takes two parameters, a MovieClip (to transform
// into BitmapData) and a String (which is the name of the Vector
// in which we will store this BitmapData).
public function load(mc:MovieClip, vecString:String):void
{
// First check if the Vector is already populated, if so, skip
// and don’t bother. If the Vector is already populated, this means
// that this MovieClip has already been loaded into memory before. Cool!
// ———————————————————————
if (this[vecString] != null) return;
// Alright, so the Vector isn’t already populated. Okay, so take the
// Vector and initialize it. Then, fill the Vector with the return
// value of a new function, convertMCToBitmapData().
// ————————————————-
this[vecString] = new Vector.<BitmapData>;
this[vecString] = convertMCToBitmapData(mc);
}
// Alright. This is the function that manipulates the MovieClip into BitmapData.
// It first takes the MovieClip and iterates through all of it’s frames. While it
// does this, the function takes notw of the maximum width and height of the
// MovieClip. This maximum width and height is then used in the second part of
// the function where BitmapData object is initialized (it requires a width/height
// parameter). After this, the built-in draw() function is used to convert each
// frame of the MovieClip into BitmapData. Finally, this BitmapData is pushed into
// a Vector which is eventually returned as the result of this function.
private function convertMCToBitmapData(mc:MovieClip):Vector.<BitmapData>
{
// Initialize variables we’ll need for this function.
// Not much to go into here, as we’ll see these all later.
// ——————————————————-
var curFrame:int = 1;
var maxWidth:int = 1;
var maxHeight:int = 1;
var boundsWidth:int = 1;
var boundsHeight:int = 1;
var bmpData:BitmapData;
var vec:Vector.<BitmapData>;
vec = new Vector.<BitmapData>();
// Alright, first part of this function. Time to get the
// maximum width and height of the MovieClip. Using a while
// loop, we will iterate through each frame of the MovieClip,
// noting the maximum width/height we come across. (This is
// where that “global parent” reference is used, since the
// getBounds() function requires it.
// ———————————
while (curFrame <= mc.totalFrames)
{
mc.gotoAndStop(curFrame);
boundsWidth = mc.getBounds(levelRef).width;
boundsHeight = mc.getBounds(levelRef).height;
if (boundsWidth > maxWidth) maxWidth = boundsWidth;
if (boundsHeight > maxHeight) maxHeight = boundsHeight;
curFrame++;
}
// Now that we have the maximum width/height stored appropriately
// inside of maxWidth and maxHeight, it’s time to move onto the next
// part. Here we are once again using a while loop to iterate through
// each frame in order to translate each frame of the MovieClip into
// BitmapData using the draw() function.
// ————————————-
curFrame = 1;
while (curFrame <= mc.totalFrames)
{
mc.gotoAndStop(curFrame);
bmpData = new BitmapData(maxWidth, maxHeight, true, 0x00000000);
bmpData.draw(mc);
vec.push(bmpData);
curFrame++;
}
// Finally, return the Vector of BitmapData.
// —————————————–
return vec;
}
}
}
[/cc]
Now let’s move on to the next class, BMPObject.
BMPObject.as
This class will be extended from all of your game objects that will be utilizing Bitmaps instead of traditional MovieClips. So, for example, the Player class, Bullet class, etc. The real purpose of this class is to make it easy for you to assign a Vector of BitmapData from the BMPLoader class (above) to a particular object as well as make it easy for you to iterate through said Vector to animate the Bitmap – just as if it were a MovieClip. Here’s the entire class – again fully commented.
[cc lang=”actionscript3″ escaped=”true”]
package
{
// Imports for this class.
// Nothing more to say really ;).
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.MovieClip;
import flash.display.Sprite;
public class BMPObject extends Sprite
{
// Here are the properties of this class. Note how
// they’re all PROTECTED – this is important. The Vector
// “vecMyBitmap” will reference a Vector in the BMPLoader
// class and will hold the BitmapData of this object. The
// “bmp” variable will be the Bitmap – the actual “face” of
// this object. Finally, the last variable, “bmpFrame,” simply
// stores the current position in the vector (used for animation).
protected var vecMyBitmap:Vector.<BitmapData>;
protected var bmp:Bitmap;
protected var bmpFrame:int = 0;
// The constructor does nothing.
// Very simple, no?
public function BMPObject()
{
}
// Again, the destroy() function, just a personal
// preference of mine to help with garbage disposal
// and the freeing of memory. Here I just remove the
// Bitmap and null the Vector and Bitmap.
public function destroy():void
{
vecMyBitmap = null;
if (bmp)
{
removeChild(bmp);
bmp = null;
}
}
// The setupBitmap() function is called to, well, setup
// the Bitmap for this object. It takes the first value in
// the Vector of BitmapData and creates a Bitmap out of it.
// The Bitmap is then added as a child to this object and then
// moved so it is centered properly.
protected function setupBitmap():void
{
bmp = new Bitmap(vecMyBitmap[bmpFrame]);
addChild(bmp);
bmp.x = -bmp.width * 0.5;
bmp.y = -bmp.height * 0.5;
}
// The animateBitmap() function, well… take a guess what
// it does. It animates the Bitmap by iterating through the
// “frames” of the Vector (and loops to the beginning when it
// reaches the end.
protected function animateBitmap():void
{
// Move position along in the vector.
// ———————————-
if (bmpFrame < vecMyBitmap.length-1) bmpFrame++;
else bmpFrame = 0;
// Change bitmapData of bitmap.
// —————————–
bmp.bitmapData = vecMyBitmap[bmpFrame];
}
}
}
[/cc]
Still with me here? Good, because we’re pretty much done at this point, now we just get to use the two classes we’ve created!
How to use these classes
Alright, now we get to the good part. How do you use these classes? It’s simple, really.
First, you obviously need to initialize the BMPLoader class. I typically do this in the parent of all of my game objects, typically the Level object or something.
[cc lang=”actionscript3″ escaped=”true”]
bmpLoader = new BMPLoader(this);
[/cc]
Then just place this code in the constructor (or init() or whatever) function of your game object (your Player, Bullet, Coin, etc):
[cc lang=”actionscript3″ escaped=”true”]
bmpLoader.load(new PlayerSkin(), “vecPlayer”); // Obviously change these to your needs.
vecMyBitmap = bmpLoader.vecPlayer;
setupBitmap();
[/cc]
As the comment says in the code, obviously change new PlayerSkin() to the MovieClip you wish to turn into BitmapData and vecPlayer to the name of the Vector inside of BMPLoader in which to store said BitmapData.
Then, in your game object’s loop function, you can simply call the animateBitmap() function and the BMPObject class will animate your object just as if it were a MovieClip. Neat, huh?
Conclusion
Now, there are three major things you must remember by utilizing this method:
- In order to be able to convert a MovieClip into BitmapData – the MovieClip’s registration point must be set to (0,0). To avoid clipping, your MovieClip art must at no point in any of it’s frames extend less than the x = 0 or y = 0 point. If it does, the BMPLoader class will produce a Bitmap of your MovieClip which is clipped (and not very pretty-looking).
- Be aware to not attempt to populate a Vector in the BMPLoader that doesn’t exist or you’ll obviously get an error. For example, doing bmpLoader.load(new PlayerSkin(), “vecPlayer”); and not having a vecPlayer object in BMPLoader will cause you some problems.
- Since your game will no longer be using crisp-looking vector-based graphics, you may encounter times in your game where your art doesn’t look as good as it does in the Flash IDE when you were drawing it. This is normal, since you’re now using Bitmaps. Your art will look perfectly fine as long as you don’t zoom in or rotate it during gameplay. I’m not saying you can’t do those things, but if you do the art will become slightly jagged-looking. Not necessarily ugly (unless you zoom in 100x’s or something), but it’ll be noticeable.
Alright, I think I’m done here. I hope if you found this article by Googling a problem you’ve been having that it’s helped you out. If not, tough noogies. Well, either that or leave a comment and I’ll see if I can help ;).
I’ve been writing this post for two hours now. That’s insane (for me). Excuse me while I go rest my back and watch some TV before dinner.
PS – Today’s the 1 month anniversary of my back surgery. Pretty cool, huh?
PPS – First Weekly Game Update in a long time scheduled for tomorrow. Pretty cool, huh?
PPPS – Pretty cool, huh?