8bitrocket.com
30Jan/080

Tutorial: Using Flash CS3 and Actionscript 3 to create Atari 7800 Asteroids Part 2

In Part 1 we covered creating an array of pre-calculated vector values for 36 angles. These 36 angles were used to move the player ship on the screen in one of the 36 rotated directions. We also covered a basic animation cache from a library object by rotating its container and taking a BitmapData snapshot of each frame. We then created a simple game engine and got the player ship moving about the screen, thrusting in the right direction. We applied some simple friction and inertia principles and used a BLIT technique on every frame to to copyPixels to a single display object.

In Part 2, we will take a smaller bite and cover how to cache the animation of a library object with a frame based animation for our asteroid rocks. We will add the rocks to our game loop and see them animating on the screen, running through their cached animation frames and being BLITTED to our single display object with copy pixels.

I created the rock frames by in a round about manner. I am using the Atari 7800 version of Asteroids as my basis for this game, so I started by firing up my MESS emulator and playing the game. The 7800 version of Asteroids is not as well known as the arcade version, but it was my favorite version, and one of the best games released for the 7800. It has multiple different colored and shaded rocks to shoot, but I just need one for the tutorial, so I chose to model the green rock. To do this, I decided to record the screen while I played the game. I fired up my copy of Replay Video, and had it record the screen for 30 seconds while I played 7800 Asteroids in MESS. I then brought the video into Sony Vegas for editing. I dropped the video into the timeline in Vegas, and used the frame advance scroll bar to move a couple frames at a time through the video of the game play. I zoomed in on the green rock and at each key frame change, I saved a JPEG of the screen. I pulled all of these JPEGs into Fireworks and editing them each down to an 18x16 image. Then, I deleted the background of each frame (there were 12 good frames). I save each one out as a .PNG file and imported all of them into Flash CS3. I built clip in the library called "rock" with one frame by putting a PNG on each of 12 key frames.

The rock clip in the library looks like this (at 200% zoom):

As you can see, it is at 0,0 on the stage. To actually cache a library clip with timeline animation, we must add this clip to a holder clip that we can export. The holder clip is called "rockToCache" in the library, and its stage looks like this:

The "rockToCache" clip has an instance of the "rock" clip set at 0,0. I increased the size from 18x16 to 36x32. That will make it more pixilated, but I think the other version was too small. If this was for a commercial game, I might clean up all of the frames by tracing over vectors before and then zoom in if needed. I gave the ":rock" clip an instance name of "clip" and set the "rockToCache" linkage class name to "RockToCache".

Now, on to the Actionscript needed to cache he animation ands add rocks to our game loop.

In our Main.as class, I have added these lines in the variable declaration section (below the class definition and before the constructor).

[cc lang="javascript" width="550"]
//** part 2 variables
var aAsteroidAnimation:Array=[];
var asteroidAnimationTimer:Timer;
var asteroidHolder:RockToCache;
var asteroidFrames:int=12;
var aRock:Array=[];
var level:int=1;
var rockPoint:Point=new Point(0,0);
var rockRect:Rectangle=new Rectangle(0,0,36,36);
var levelRocksCreated:Boolean=false;
[/cc]

aAsteroidAnimation is an array that will hold 12 BitmapData objects representing the 12 frames of animation in the rock clip.
asteroidAnimationTimer is a Timer object that that will count up to 12.
asteroidHolder is an instance of the RockToCache library clip. We only need one and it will be used exclusively as a holder to run through the "rock" frames.
aRock
is an array that will hold all of the instances of our rock objects for game play.
level is a integer that will hold the current game play level. Eventually, when we add in collision detection, we will have the game move to a new level when all of the rocks are cleared.
rockPoint is a pre instantiated point object used in the copyPixels operation for all rocks to the canvasBD. The rockPoint will be changed to the x and y coordinates of the current rock before the copyPixels is completed.
rockRect is a static rectangle instance that simply represents a bounding box for the max size of our rock (36x36).
levelRocksCreated is the first of our game control variables. When we start a new level, this will need to be set to false;

[cc lang="javascript" width="550"]
private function animationTimerCompleteHandler(e:TimerEvent):void {
createAsteroidAnimation();
//startGame();
}

[/cc]That sets up the variables needed for this part. Now, there was a change to the animationTimerCompleteHandler from part 1. We have commented out the startGame() call and have replaced it with a call to a new createAsteroidAnimation() function.

[cc lang="javascript" width="550"]
private function createAsteroidAnimation():void {
animationCounter=1; // match frame on timeline
asteroidHolder=new RockToCache();
asteroidHolder.x=100;
asteroidHolder.y=100;
addChild(asteroidHolder);
asteroidAnimationTimer=new Timer(1,asteroidFrames);
asteroidAnimationTimer.addEventListener(TimerEvent.TIMER, cacheAsteroid);
asteroidAnimationTimer.addEventListener(TimerEvent.TIMER_COMPLETE,
asteroidAnimationTimerCompleteHandler);
asteroidAnimationTimer.start();
}

[/cc]

The createAsteroidAnimation() function sets up the Timer based loop that will jump to each frame of the "ship" library clip and cache (or store) the pixel data from that frame in a BitmapData object. In part 1, we stored all of the rotations of the ship clip my manipulating the rotation property of its holder clip called "shipHolder". For the rock, we will use a different method. On each iteration we will "gotoAndStop()" the next frame on the timeline and "draw()" those pixels to a BitmapData object, and then add that object to our aAsteroidAnimation. I did the rock this way because it is the most likely delivery method that a designer might give you for a tween-like animation. We will explore one more method of creating an array of BitmapData objects when we use a sprite sheet to create the animation loop for the playerShip's missiles.

animationCounter=1;
First we set out animationCounter to be 1. We don't start with 0 because we want to make it easier on ourselves to follow the timeline frame of the rock. Since the timeline is "1 based" rather than "0 based" we set this to 1.

asteroidHolder=new RockToCache();
We create an new instance of out RockToCache library item. Remember, this is basically a holder for a instance of our rock library item named "clip".

asteroidHolder.x=100;
asteroidHolder.y=100;
addChild(asteroidHolder);

These merely put the holder on the screen in a place we can see it. If we set the frame rate of the movie very low, we can see the actual cache happen. If not, this location doesn't matter. We also add the asteroidHolder to the display list to make sure we can see it and cache its contents.

asteroidAnimationTimer=new Timer(1,asteroidFrames);
asteroidAnimationTimer.addEventListener(TimerEvent.TIMER, cacheAsteroid);
asteroidAnimationTimer.addEventListener(TimerEvent.TIMER_COMPLETE, asteroidAnimationTimerCompleteHandler);
asteroidAnimationTimer.start();
These 4 lines create a timer that will run 12 times. On each of the 12 iterations it will call the cacheAsteroid method and when it is complete with the 12 iterations it will call the asteroidAnimationTimerCompleteHandler. The final line starts the timer on its way.

 

[cc lang="javascript" width="550"]
private function cacheAsteroid(e:TimerEvent):void {
asteroidHolder.clip.gotoAndStop(animationCounter);
var spriteMapBitmap:BitmapData=new BitmapData(36,36,true,0x00000000);
spriteMapBitmap.draw(asteroidHolder,new Matrix());
aAsteroidAnimation.push(spriteMapBitmap);
//trace("caching " + animationCounter);
animationCounter++;

}

private function asteroidAnimationTimerCompleteHandler(e:TimerEvent):void {
trace("cacheAsteroid 7");
startGame();
}

[/cc]

The cacheAsteroid() method is called 12 times and on each call it draws the current frame of the rock clip (referenced as asteroidHolder.clip) to a new Bitmapdata object. That object is added to the aAsteroidAnimation array.

asteroidHolder.clip.gotoAndStop(animationCounter);
This line simply instructs the clip instance of the "rock" clip inside the asteroidHolder to move to the current frame represented by the animationCounter;

var spriteMapBitmap:BitmapData=new BitmapData(36,36,true,0x00000000);
Here we create a new BitmapData object and set it to 36x36 with transparency. And set the transparency threshold at 0 and the background fill color to black. Since the threshold is 0, we don't see any of the background color show through.

spriteMapBitmap.draw(asteroidHolder,new Matrix());
Here we draw the current state of the asteroidHolder to the new BitmapData object. The asteroidHolder contains the "clip" which is an instance of the rock library item that has been moved to the current frame of animation.

aAsteroidAnimation.push(spriteMapBitmap);
animationCounter++;

These two lines simply add the BitmapData object to the aAsteroidAnimation array and increase the animationCounter so the gotoAndStop will go to the next frame on the next interval.

The asteroidAnimationTimerCompleteHandler() method simply starts our game. The method that starts our game will change as we add to the game. It will always be the last method of the setup phase that calls startGame.

 

[cc lang="javascript" width="550"]
private function runGame(e:TimerEvent) {
checkKeys();
updatePlayer();
updateRocks();
drawBackground();
drawPlayer();
drawRocks();
}

[/cc]

The runGame() method has been modified from the part 1 version. We have added 2 functions to handle rocks. The updateRocks() method does all of the work in creating the initial rocks for the game (or level) and also updates all of the rocks on the screen based on their individual dx,dy, and speed variables.

 

[cc lang="javascript" width="550"]
private function updateRocks():void {
if (!levelRocksCreated) {
for (var ctr:int=0;ctr private function drawRocks() {
var rockLen:int=aRock.length-1;
var ctr:int;

for each (var tempRock:Object in aRock) {

rockPoint.x=tempRock.x;
rockPoint.y=tempRock.y;

canvasBD.copyPixels(aAsteroidAnimation[tempRock.animationIndex],
rockRect, rockPoint);

tempRock.frameCount++;
if (tempRock.frameCount > tempRock.frameDelay){

tempRock.animationIndex++;
if (tempRock.animationIndex > asteroidFrames-1) {
tempRock.animationIndex = 0;
}
tempRock.frameCount=0;
}
}

}

[/cc]

The final new method in part 2 draws the rocks to the canvasBD. It's basic job is to loop through all of the rocks, set the rockPoint variable to be the location of the current rocks upper left-hand corner, and then BLIT the pixels of the rock to the canvasBD.

Finally, after the BLIT, it increases the frameCount for the rock. If the frameCount is greater than the frameDelay, it changes the animation index for the next game timer tick. It checks to make such it hasn't increased passed its upper bounds, and if it has, it sets the animationIndex to 0.

That's it, below is the new working example. Use the up arrow to apply the thrust, and the right and left arrows to simulate the rotation of the ship.

 

The source files are here: 7800 Asteroids Tutorial Part 2 source files

Read Part 3

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

No comments yet.


Leave a comment

No trackbacks yet.

This site is protected by Comment SPAM Wiper.