I never thought development of web games can be that simple until I started development of my first game with Adobe Flex. I made two games, without any prior experience with Action script, in only ten months: "Fitter" and "RGB".

I found many answers to my questions on different forums and web sites, and it is time to share my knowledge with Flex community.

Wednesday, April 28, 2010

Custom progress bar

Previous 24 chapters teaches you, how to make your own game with Flex. In the following few chapters I will focus mostly on the alternatives, that should considered, before you submit or publish your own product. I call them alternatives, because there are some disadvantages, you should consider, before you implement them. Of course both advantages and disadvantages will be mentioned in the tutorial.



In chapter 10 it was explained, how to embed SWF file into the HTML web page. Now let's presume you have made a web page where you have publish the demo of your game. Everything works fine, even the progress bar is displayed, while the game is downloading. This is the default progress bar that appears in many Flex applications. Player will most probably see it only the first time he/she enters the web page. The next time, demo will load immediately, because it was stored on the local file system, after it was downloaded for the first time.

You may be asking, why bother implementing custom progress bar, that will be visible only for few seconds and will not affect the players experience at all. Well, the best answer, I can give you, is: because you can. Snake's Adventure is my third game made with Flex, and I got a little tired of watching the same progress bar, I have also seen in numerous other games and demos. I just had to do something about it.

I hope that you have noticed, how the new progress bar in game, embedded above, works. If you did not pay attention before, you can reload the page and you will most probably see the progress bar for one or two seconds, because the SWF file is already cached. If you wish to see the slower version (depends on the bandwidth of your internet connection) you can clear the browsers cache or open this page in different browser. You can also visit Snake's Adventure official web page on my website.

Anyway, let's focus on implementation of the custom progress bar. Custom progress bar has to be implemented in its own class. Class and all embedded resources, that are required to display the progress bar, are stored at the beginning of SWF file. Until this part of SWF file is downloaded, area taken by embedded file in the web page, remains blank. It is wise to keep the size of embedded resources at minumum (use JPEG format for background image and please, do not try to play mp3 files, while the game is downloading).

Application preloader attribute

Unless it is not defined, that custom progress bar should be displayed during download, default progress bar will be displayed. Inside the main source file (.mxml) we have to define, which class contains the implementation of the custom progress bar. preloader attribute inside mx:Application element at the top of SnakesAdventure.mxml, defines the class:

preloader="Classes.PreLoader"

class PreLoader

Implementation of custom progress bar can be found in PreLoader.as file, which is located inside Classes folder. You can view complete file with additional comments, if you download the project package of the final version of the Snake's adventure. This is the short (but fully functional) version of PreLoader.as:

package Classes
{
  import flash.geom.*;
  import flash.display.*;
  import flash.utils.*;
  import flash.events.*;
  import flash.net.*;
  import mx.preloaders.*;
  import mx.events.*;
  import mx.controls.*;

  public class PreLoader extends Sprite implements IPreloaderDisplay
  {
    protected var _msecMinimumDuration:Number = 1000;
    protected var _IsInitComplete:Boolean = false;
    protected var _timer:Timer;
    protected var _bytesLoaded:uint = 0;
    protected var _bytesExpected:uint = 1;
    protected var _fractionLoaded:Number = 0;

    public function PreLoader()
    {
      super();
    }

    private var screenBuffer:BitmapData = null;

    virtual protected function draw():void
    {
      screenBuffer.copyPixels(titleScreenBitmap,
                    new Rectangle(0, 0, 480, 480),
                    new Point(0,0));

      screenBuffer.copyPixels(progressBitmap,
                    new Rectangle(21, 8, 286 * _fractionLoaded, 36),
                    new Point(96,305));

      screenBuffer.copyPixels(progressBitmap,
                    new Rectangle(0, 50, 330, 70),
                    new Point(75,290));

      graphics.clear();
      graphics.beginBitmapFill(screenBuffer, null, false, false);
      graphics.drawRect(0, 0, 480, 480);
      graphics.endFill();
    }

    [Embed(source="../Images/TitleScreen.jpg")] private var titleScreenImg:Class;
    private var titleScreenBitmap:BitmapData = null;

    [Embed(source="../Images/ProgressBar.png")] private var progressBarImg:Class;
    private var progressBitmap:BitmapData = null;

    virtual public function initialize():void
    {
      screenBuffer = new BitmapData(480, 480, false, 0x00000000);

      titleScreenBitmap = new BitmapData(480,480,true);
      titleScreenBitmap.draw(new titleScreenImg());

      progressBitmap = new BitmapData(330,120,true,0x00000000);
      progressBitmap.draw(new progressBarImg());

      _timer = new Timer(1);
      _timer.addEventListener(TimerEvent.TIMER, timerHandler);
      _timer.start();
    }

    protected var _preloader:Sprite;

    virtual public function set preloader(value:Sprite):void
    {
      _preloader = value;

      value.addEventListener(ProgressEvent.PROGRESS, progressHandler);
      value.addEventListener(Event.COMPLETE, completeHandler);

      value.addEventListener(FlexEvent.INIT_PROGRESS, initProgressHandler);
      value.addEventListener(FlexEvent.INIT_COMPLETE, initCompleteHandler);
    }

    virtual public function set backgroundAlpha(alpha:Number):void{}
    virtual public function get backgroundAlpha():Number { return 1; }

    protected var _backgroundColor:uint = 0xffffffff;
    virtual public function set backgroundColor(color:uint):void { _backgroundColor = color; }
    virtual public function get backgroundColor():uint { return _backgroundColor; }

    virtual public function set backgroundImage(image:Object):void {}
    virtual public function get backgroundImage():Object { return null; }

    virtual public function set backgroundSize(size:String):void {}
    virtual public function get backgroundSize():String { return "auto"; }

    protected var _stageHeight:Number = 480;
    virtual public function set stageHeight(height:Number):void { _stageHeight = height; }
    virtual public function get stageHeight():Number { return _stageHeight; }

    protected var _stageWidth:Number = 480;
    virtual public function set stageWidth(width:Number):void { _stageWidth = width; }
    virtual public function get stageWidth():Number { return _stageWidth; }

    virtual protected function progressHandler(event:ProgressEvent):void
    {
      _bytesLoaded = event.bytesLoaded;
      _bytesExpected = event.bytesTotal;
      _fractionLoaded = Number(_bytesLoaded) / Number(_bytesExpected);

      draw();
    }

    virtual protected function completeHandler(event:Event):void
    {
    }

    virtual protected function initProgressHandler(event:Event):void
    {
      draw();
    }

    virtual protected function initCompleteHandler(event:Event):void
    {
      _IsInitComplete = true;
    }

    virtual protected function timerHandler(event:Event):void
    {
      if (_IsInitComplete && getTimer() > _msecMinimumDuration)
      {
        _timer.stop();
        dispatchEvent(new Event(Event.COMPLETE));
      }
      else
        draw();
    }
  }
}

Even without comments, PreLoader class contains a lot of code. There are only few parts, that you need to understand, if you wish to create a different version of progress bar.

Embed statements take care of compiling all required resources (images) into SWF file.

Function initialize is executed right after class and resources are download. This is the perfect place to create screen buffer and all BitmapData objects with copies of embedded images. At the end, we create a Timer object, that will trigger every millisecond (only in theory, in practice it will trigger approximately every ten milliseconds) and execute timerHandler function, implemented at the bottom of the class.

In preloader function we assigned four event listeners:
- ProgressEvent.PROGRESS will execute progressHandler function from time to time as the SWF is downloading. Event has two properties bytesLoaded (size of SWF file, that has already been downloaded) and bytesTotal (total size of SWF file)
- Event.COMPLETE will execute completeHandler function, when download is finished
- FlexEvent.INIT_PROGRESS will execute initProgressHandler function from time to time as the initialization continues
- FlexEvent.INIT_COMPLETE will execute initCompleteHandler function when initialization is complete.

Function draw will be called frequently from progressHandler and timerHandler functions. Whenever draw is called, background image (TitleScreen.jpg) is copied into screen buffer, first. After that, fraction of upper portion of progress bar image (ProgressBar.png) is copied. Lower portion of progress bar image is copied over the progress bar. This is the reason that progress bar is maybe not so obvious as it should be, but you can fix this in your own version.


Finally, the content of screen buffer is copied into the graphics object of the container, that takes care of displaying progress bar on the screen.

No comments:

Post a Comment