Pin Me

Adding Bitmap Animations With Flex and Actionscript

written by: Matthew Casperson•edited by: Linda Richter•updated: 11/6/2009

In part 6 we added collision detection. In part 7 we will add animations to allow us to include some nice explosions.

  • slide 1 of 13

    In part 6 we introduced collision detection, which allowed us to destroy and crash into the enemies on the screen. But the enemies just disappeared, which was a little unsatasifying. In this article we will add animations, which in turn will allow us to include some nice explosions.

    If there is one thing Flash is famous for, it's animation. Flex does allow you to use the same vector based animations that have been so popular with Flash, however for our game we will be using good old frame based animations. There are two reasons for this. The first is that frame based animation is easier to create, and more applicable to the style of game we are making. The second is that I am not an artist and have to make do with the free artwork that I can find out on the web, which in this case is frame based :).

    bigexplosion 

    The image above shows what I mean by frame based animation. It is a series of frames that are displayed in sequence to produce an animation. Have you ever drawn a little animation in the corners of your textbook at school? It's the same concept.

    To implement animations we first need to make some changes to the GraphicsResource class. Lets take a loot at that now.

  • slide 2 of 13

    Improving the Graphics

    package

    {

    import flash.display.*;

    public class GraphicsResource

    {

    public var bitmap:BitmapData = null;

    public var bitmapAlpha:BitmapData = null;

    public var frames:int = 1;

    public var fps:Number = 0;

    public function GraphicsResource(image:DisplayObject, frames:int = 1, fps:Number = 0)

    {

    bitmap = createBitmapData(image);

    bitmapAlpha = createAlphaBitmapData(image);

    this.frames = frames;

    this.fps = fps;

    }

    protected function createBitmapData(image:DisplayObject):BitmapData

    {

    var bitmap:BitmapData = new BitmapData(image.width, image.height);

    bitmap.draw(image);

    return bitmap;

    }

    protected function createAlphaBitmapData(image:DisplayObject):BitmapData

    {

    var bitmap:BitmapData = new BitmapData(image.width, image.height);

    bitmap.draw(image, null, null, flash.display.BlendMode.ALPHA);

    return bitmap;

    }

    }

    }

  • slide 3 of 13

    The changes here are quite simple. We add two properties: frames and fps. The frames property is a count of how many frames of animation are included in the image held by the GraphicsResource. Using the explosion image above as an example, the frames would be set to 7. The fps property defines the frames per second that the animation will play.

    The GameObject class does not know about animations. If you initialized a GameObject with the image above, the entire image would be displayed on the screen rather than an animated sequence. To enable animations we need to create a new class: AnimatedGameObject. Lets look at that code now.

  • slide 4 of 13
  • slide 5 of 13

    AnimatedGameObject.as

    package

    {

    import flash.display.*;

    import flash.geom.*;

    import mx.collections.*;

    public class AnimatedGameObject extends GameObject

    {

    static public var pool:ResourcePool = new ResourcePool(NewAnimatedGameObject);

    protected var frameTime:Number = 0;

    protected var currentFrame:int = 0;

    protected var frameWidth:int = 0;

    protected var playOnce:Boolean = false;

    static public function NewAnimatedGameObject():AnimatedGameObject

    {

    return new AnimatedGameObject();

    }

    public function AnimatedGameObject()

    {

    }

    public function startupAnimatedGameObject(graphics:GraphicsResource, position:Point, z:int = 0, playOnce:Boolean = false):void

    {

    this.playOnce = playOnce;

    this.frameWidth = graphics.bitmap.width / graphics.frames;

    startupGameObject(graphics, position, z);

    }

    override public function enterFrame(dt:Number):void

    {

    if (inuse)

    {

    frameTime += dt;

    if (graphics.fps != -1)

    {

    while (frameTime > 1/graphics.fps)

    {

    frameTime -= 1/graphics.fps;

    currentFrame = (currentFrame + 1) % graphics.frames;

    if (currentFrame == 0 && playOnce)

    {

    shutdown();

    break;

    }

    }

    }

    }

    }

    override public function copyToBackBuffer(db:BitmapData):void

    {

    if (inuse)

    {

    var drawRect:Rectangle = new Rectangle(currentFrame * frameWidth, 0, frameWidth, graphics.bitmap.height);

    db.copyPixels(graphics.bitmap, drawRect, position, graphics.bitmapAlpha, new Point(drawRect.x, 0), true);

    }

    }

    override protected function setupCollision():void

    {

    collisionArea = new Rectangle(0, 0, frameWidth, graphics.bitmap.height);

    }

    }

    }

  • slide 6 of 13

    Like any other game resource, the AnimatedGameObject class extends GameObject. To play an animation we override 3 of GameObjects functions: enterFrame, copyToBackBuffer and setupCollision.

    During enterFrame an AnimatedGameObject keeps a track of how long it has been since the last frame was played. Once this reaches the value defined by the fps property of the GraphicsResource the AnimatedGameObject moves to the next frame. There is also a playOnce property, which if set to true triggers the AnimatedGameObject to remove itself from the game once one complete animation has been played. This will be useful for creating effects (like an explosion) that play once and should then disappear. It allows you as a coder to create a kind of "fire and forget" object in the game that will clean itself up.

    The copyToBackBuffer has been modified slightly from the same code in the GameObject to allow it to only copy a section of the image referenced by the grahipcs property. This section relates to the current frame, and as the section that is moves from left to right we appear to get an animation on the screen.

    Finally we have overridden the setupCollision function to define a collisionArea that is the size of one animated frame as opposed to the size of the whole image.

    The rest of the code here (like the resource pooling and startup/shutdown functions) have been discussed in previous articles, and their application here follows the same mentality.

    We also need to make some changes to the ResourceManager to accommodate the extra information required by an animation. Lets take a look at that now.

  • slide 7 of 13
  • slide 8 of 13

    ResourceManager.as

    package

    {

    import flash.display.*;

    public final class ResourceManager

    {

    [Embed(source="../media/brownplane.png")]

    public static var BrownPlane:Class;

    public static var BrownPlaneGraphics:GraphicsResource = new GraphicsResource(new BrownPlane(), 3, 20);

    [Embed(source="../media/smallgreenplane.png")]

    public static var SmallGreenPlane:Class;

    public static var SmallGreenPlaneGraphics:GraphicsResource = new GraphicsResource(new SmallGreenPlane(), 3, 20);

    [Embed(source="../media/smallblueplane.png")]

    public static var SmallBluePlane:Class;

    public static var SmallBluePlaneGraphics:GraphicsResource = new GraphicsResource(new SmallBluePlane(), 3, 20);

    [Embed(source="../media/smallwhiteplane.png")]

    public static var SmallWhitePlane:Class;

    public static var SmallWhitePlaneGraphics:GraphicsResource = new GraphicsResource(new SmallWhitePlane(), 3, 20);

    [Embed(source="../media/bigexplosion.png")]

    public static var BigExplosion:Class;

    public static var BigExplosionGraphics:GraphicsResource = new GraphicsResource(new BigExplosion(), 7, 20);

    [Embed(source="../media/smallisland.png")]

    public static var SmallIsland:Class;

    public static var SmallIslandGraphics:GraphicsResource = new GraphicsResource(new SmallIsland());

    [Embed(source="../media/bigisland.png")]

    public static var BigIsland:Class;

    public static var BigIslandGraphics:GraphicsResource = new GraphicsResource(new BigIsland());

    [Embed(source="../media/volcanoisland.png")]

    public static var VolcanoIsland:Class;

    public static var VolcanoIslandGraphics:GraphicsResource = new GraphicsResource(new VolcanoIsland());

    [Embed(source="../media/twobullets.png")]

    public static var TwoBullets:Class;

    public static var TwoBulletsGraphics:GraphicsResource = new GraphicsResource(new TwoBullets());

    [Embed(source="../media/cloud.png")]

    public static var Cloud:Class;

    public static var CloudGraphics:GraphicsResource = new GraphicsResource(new Cloud());

    }

    }

  • slide 9 of 13

    As you can see all we have done is to add values for frames and fps in the GraphicsResource constructor for those images that will now be animations.

    The final step is to modify the game elements that should now be animated to extend the AnimatedGameObject class instead of GameObject, and to create some explosions when we destroy a plane.

    Both the Player and Enemy classes will be animated, and both will create an explosion when they are destroyed. The code changes are the same for both classes, so I will show only the new Enemy class. You can download the source code at the end of the article to see the changes made to the Player class.

  • slide 10 of 13
  • slide 11 of 13

    Enemy.as

    package

    {

    import flash.geom.Point;

    import mx.core.*;

    public class Enemy extends AnimatedGameObject

    {

    static public var pool:ResourcePool = new ResourcePool(NewEnemy);

    protected var logic:Function = null;

    protected var speed:Number = 0;

    static public function NewEnemy():Enemy

    {

    return new Enemy();

    }

    public function Enemy()

    {

    super();

    }

    public function startupBasicEnemy(graphics:GraphicsResource, position:Point, speed:Number):void

    {

    super.startupAnimatedGameObject(graphics, position, ZOrders.PlayerZOrder);

    logic = basicEnemyLogic;

    this.speed = speed;

    this.collisionName = CollisionIdentifiers.ENEMY;

    }

    override public function shutdown():void

    {

    super.shutdown();

    logic = null;

    }

    override public function enterFrame(dt:Number):void

    {

    super.enterFrame(dt);

    if (logic != null)

    logic(dt);

    }

    protected function basicEnemyLogic(dt:Number):void

    {

    if (position.y > Application.application.height + graphics.bitmap.height )

    this.shutdown();

    position.y += speed * dt;

    }

    override public function collision(other:GameObject):void

    {

    var animatedGameObject:AnimatedGameObject = AnimatedGameObject.pool.ItemFromPool as AnimatedGameObject;

    animatedGameObject.startupAnimatedGameObject(

    ResourceManager.BigExplosionGraphics,

    new Point(

    position.x + graphics.bitmap.width / graphics.frames / 2 - ResourceManager.BigExplosionGraphics .bitmap.width / ResourceManager.BigExplosionGraphics.frames / 2,

    position.y + graphics.bitmap.height / 2 - ResourceManager.BigExplosionGraphics .bitmap.height / 2),

    ZOrders.PlayerZOrder,

    true);

    this.shutdown();

    }

    }

    }

  • slide 12 of 13

    By extending AnimatedGameObject instead of GameObject we allow the Enemy class to be animated. This also requires calling super.startupAnimatedGameObject instead of super.startupGameObject in our startup function.

    The only other change is in the collision function, where we create an AnimatedGameObject to represent the explosion. Notice that we set playOnce to true in the constructor. This means that the explosion will be created, then play through once before removing itself from the game.

    Animations add a whole new level of detail to a game. By creating the AnimatedGameObject class we now have the ability to quickly and easily add animated object to the game. The only thing missing now are some sound effects, which we will add in part 8.

    Go back to Flash Game Development with Flex and ActionScript

  • slide 13 of 13