Accessibility

Table of Contents

Creating a preloader in Flash

Turning MovieClip into PreloaderClip

In the previous section of this article, I illustrated how you can employ multiple listeners to respond to a single event in different ways. I also provided an example of creating a custom event, and showed how movie clips can dispatch events to indicate the completion of activity within the Timeline. This strategy makes it possible to call functions at a specific time, which is exactly what is needed for your preloader to accurately display the percent loaded graphically to the user. In the code examples, I also touched on the concept of using an import statement to make an external class available to the project. Now you'll take a closer look at ActionScript classes and see why modular coding practices are useful.

Class structure

Classes are fundamentally just a different way of writing code. For the uninitiated, moving from simple coding to developing classes can be mortifying. Curiously, with experience, you'll find that ActionScript classes are far easier to read and understand than the simple, quick coding you're used to.

A simple class looks like this:

package
{
 public class MyClass
 {
     var myVar:String  // this is a class variable, otherwise known as a property.
     function MyClass() { } // this is called the constructor. When you create new MyClass(), this function automatically runs.
     function doSomething() {} // this is a class function, aka a method. I could call this to tell the class to quite literally do something.
 }
}

Classes can utilize other classes by using import, which I've already utilized on the Timeline. In our example, event hooks imported com.bigspaceship.events.AnimationEvent.

There are many books that cover classes and object-oriented programming in Flash, but if I could point to one resource that helped me understand all of this, Colin Moock's Essential ActionScript 3 would be it. Colin's book is at my desk at all times.

Building the PreloaderClip engine

PreloaderClip is a very specific kind of movie clip. It has all of the regular movie clip methods (startDrag and stopDrag, for example) and properties (x, y, and alpha), but it also has its own custom functionality to handle loading.

In order to utilize the MovieClip class in this way, we'll need to extend it. Here's how to do that:

package com.bigspaceship.frameworks.site
{
  import flash.display.MovieClip
  public class PreloaderClip extends MovieClip
  {
      function PreloaderClip() {};
  };
};

The key syntax is in line 4 above: extends MovieClip. PreloaderClip extends the MovieClip class; it's a movie clip and then some. Makes sense, right?

PreloaderClip has stop(); on the first frame. That way the preloader artwork won't appear unless I command it to do so. I want it to play that section with the frame label "IN" in the Timeline, so I'll make a simple method to do that:

public function animateIn():void
{
  gotoAndPlay("IN");
  addEventListener(AnimationEvent.ANIMATE_IN,
    _onPreloaderIn,false,0,true);
};

This method tells the timeline to play "IN" and then listen for my event. When the event happens, it automatically calls _onPreloaderIn because that's the method I specified to handle AnimationEvent.ANIMATE_IN. Here's what _onPreloaderIn looks like:

private function _onPreloaderIn($evt:Event):void
{
    progress_mc.addEventListener(Event.COMPLETE,_onProgressBarComplete,false,0,true);
    progress_mc.addEventListener(Event.ENTER_FRAME,_onProgressEnterFrame,false,0,true);
    progress_mc.stop();
    dispatchEvent(new Event(Event.INIT));
};

PreloaderClip assumes a movie clip is nested inside its Timeline named progress_mc. I tell progress_mc to stop (in case it decides to display the load progress before I'm ready) and listen to two new events: Event.COMPLETE and Event.ENTER_FRAME. Now ENTER_FRAME is the new way to handle onEnterFrame: every frame _onProgressEnterFrame will be called. (More on that in just a second.) Event.COMPLETE is an event hook at the end of the progress_mc timeline. When progress_mc gets to the end of that timeline, loading is finished.

That dispatchEvent line is an important one. Here the PreloaderClip is telling any listeners that something happened. Just like my event hooks, I can addEventListener() somewhere else to know when the preloader is ready and to ensure that I can begin loading.

Let's look at the method I've mapped ENTER_FRAME to, namely _onProgressEnterFrame:

private function _onProgressEnterFrame($evt:Event):void
{
     (_targetFrame > progress_mc.currentFrame) ? progress_mc.play() : progress_mc.stop();
     var totalPct:Number = Math.round((progress_mc.currentFrame/progress_mc.totalFrames) * 100);
     try
     {
        pct_mc.tf.text = totalPct.toString();  
     }
     catch($error:Error)
     {
        Out.debug(this,"% loaded: " + totalPct.toString());
     }
};

On every frame I'll compare _targetFrame against progress_mc's currentFrame. If my target frame is greater than the current frame, then play; otherwise stop. In addition, I wrote a simple way to display the percent loaded in a text field to the user. If the text field exists (in this case it should be nested in a movie clip named pct_mc and given an instance name of tf), then it will write the total percent in the text field. Otherwise I'll just trace the information to the Output panel.

The next part involves figuring out targetFrame. Here's how this is accomplished:

public function updateProgress($bytesLoaded:Number,
    $bytesTotal:Number, $itemsLoaded:Number, $itemsTotal:Number):void
{
  var framesPerItem:Number = Math.floor(progress_mc.totalFrames/($itemsTotal-1));
  var pct:Number = $bytesLoaded/$bytesTotal;
  _targetFrame = Math.floor(framesPerItem * pct) + (framesPerItem * $itemsLoaded);
};

Ahhhhh, the secret is revealed. PreloaderClip actually doesn't load anything. It just displays the progress! I'll load the files I need from somewhere else—somewhere external. As data loads in, I'll tell PreloaderClip the following:

  • The bytesLoaded of the current loading item
  • The bytesTotal of that same item
  • The current item I'm loading
  • The total number of items loaded

In other words, I'll load each item sequentially. If I have an XML file, a SWF, and a JPEG to load, I'll first load the XML. The current item I'm loading is 0 of 2 (remember, always count from 0!). Then when the XML load is finished, I'll load a SWF. That item is counted as 1 of 2. Finally, I'll load the JPEG file. The current item I'm loading is 2 of 2. When the JPEG finishes loading, progress_mc will be at the end of its timeline. The user will know that the content is 100% loaded and PreloaderClip will receive the Event.COMPLETE event, which will automatically fire _onProgressBarComplete():

private function _onProgressBarComplete($evt:Event = null):void
{
  _isLoadComplete = true;
  
  progress_mc.removeEventListener(Event.ENTER_FRAME,_onProgressEnterFrame); _animateOut();
};
private function _animateOut():void
{
  gotoAndPlay("OUT");
  addEventListener(AnimationEvent.ANIMATE_OUT, _onPreloaderOut,false,0,true);        
};

In the function above, preloading is complete, so we play the section of the timeline with the "OUT" frame label. When we hit the event hook AnimationEvent.ANIMATE_OUT, _onPreloaderOut is called:

private function _onPreloaderOut($evt:Event):void
{
  // kill listeners. this will prep the loader for the next use.
  // if i didn't do this, I might get _onPreloaderIn() twice when I reach the ANIMATE_IN event hook. that would mess everything up.
  
    removeEventListener(AnimationEvent.ANIMATE_IN,_onPreloaderIn);  
    removeEventListener(AnimationEvent.ANIMATE_OUT,_onPreloaderOut);
  if(_isLoadComplete) dispatchEvent(new Event(Event.COMPLETE));
  else animateIn();        
};

In the function above, I cleared the listeners out and dispatched the Event.COMPLETE event. Now any listeners in my program will know the PreloaderClip is finished and the Stage is clear to display the loaded content.

Taking the preloader for a test drive

I've got another class named Main, which will be my Document Root class. The Document Root is the main Timeline's class. When the SWF loads, the Document Root will be the controlling class. To set this, click anywhere on the Stage and then type the classpath to whatever class you want to use. In my case, I won't bury the Main class in a package; I'll simply call it Main and save my file to _classes/Main.as.

Let's take a look at Main:

public function Main()
{
  Out.enableAllLevels();
  Out.status(this,"Ready to go!");
  // first let's start by setting our PreloaderClip to tell us
    when it's finished. remember, Main is our main Timeline and preloader_mc is a
    PreloaderClip.
  preloader_mc.animateIn();
  preloader_mc.addEventListener(Event.INIT,_onPreloaderIn,false,0,true);
 
    preloader_mc.addEventListener(Event.COMPLETE,_onPreloaderOut,false,0,true);  
};
private function _onPreloaderIn($evt:Event):void
{
  _loadCount = 0;
  // this is how to load SWFs, JPGs and such in AS3.
  _swfLoader = new Loader();
  
    _swfLoader.contentLoaderInfo.addEventListener(Event.INIT,_onSWFLoaded,false,0,true);
    _swfLoader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS,_onLoadProgress,false,0,true);
  _swfLoader.load(new URLRequest("loadedContent.swf"));     
};

When the constructor is called (that is, when the SWF initializes), I know I'll have a PreloaderClip on my main Timeline named preloader_mc. I'll tell preloader_mc to play its "IN" animation and have Main listen for when it is finished. Then I'll set up a Loader, which will do all the actual loading. The Loader will listen for load progress (ProgressEvent.PROGRESS), which will fire Main's _onLoadProgress function. The function looks like this:

private function _onLoadProgress($evt:Event):void
{
  Out.status(this,"_onLoadProgress");
  // this is all we need to display load progress.
  preloader_mc.updateProgress($evt.target.bytesLoaded,$evt.target.bytesTotal,_loadCount,_loadTotal);  
};

The value of _loadCount is 0 when loading the SWF and _loadTotal is 1. I know this because I'm only loading two files. Therefore, PreloaderClip will designate half of the actual progress to this SWF loading. When the SWF finishes loading, Main is ready and waiting with _onSWFLoaded. Here's what it looks like:

private function _onSWFLoaded($evt:Event):void
{
  Out.status(this,"_onSWFLoaded");
  _loadCount = 1;
  // so now we can start to load the xml. here's how in as3.
  // note that we can use the same _onProgress function for
    both SWF and XML.
  _xmlLoader = new URLLoader();
  
    _xmlLoader.addEventListener(Event.COMPLETE,_onXMLLoaded,false,0,true);
  
    _xmlLoader.addEventListener(ProgressEvent.PROGRESS,_onLoadProgress,false,0,true);
  _xmlLoader.load(new URLRequest("content.xml"));
};

Now I'm loading an XML file, named content.xml. Note that the XML Loader is calling the same method in Main to handle the progress: _onLoadProgress. One line of code in one function handles all the visual updating. The only difference now is that the value of _loadCount is 1 instead of 0. The progress movieclip is dedicated to 0–100% of total load.

When the XML file content.xml finishes loading, _onXMLLoaded is called. Here's what the function looks like:

private function _onXMLLoaded($evt:Event):void
{
  preloader_mc.setComplete();
};

That's it. The preloader knows to finish the preloader and, as it animates out, Main is already listening for PreloaderClip.ANIMATE_OUT:

private function _onPreloaderOut($evt:Event):void
{
  var loadedSWF:MovieClip = _swfLoader.contentLoaderInfo.content;
  addChild(loadedSWF);           
  // the SWF is added to the stage.
  loadedSWF.gotoAndPlay("IN");
  // finally something looks familiar, right? the loaded SWF will play it's in label.
  
  loadedSWF.addEventListener(AnimationEvent.ANIMATE_IN,_onLoadedSWFAnimateIn,false,0,true);
};

The loaded SWF is added to the Stage and begins to play. That's all there is to it!

Bringing classes into Flash

How does Flash know where to find these external classes? By default, Flash looks in the base directory of your FLA file to find the classes. I personally dislike placing classes in this location. As a best practice, organize your classes into their own obvious directory. Here's how you can do that.

Select File > Publish Settings. Click the Flash tab and then click the Settings button next to the ActionScript version—which should be set to ActionScript 3, by the way (see Figure 2).

Clicking the Settings button to access the Settings dialog box

Figure 2. Clicking the Settings button to access the Settings dialog box

Upon clicking Settings, the ActionScript 3 Settings dialog box appears. Click the plus (+) button to add a new classpath. For this example, I'm going to put all of my classes in a directory relative to my FLA file named classes. So I'll type ./classes/ as the new classpath. (The trailing slash shouldn't matter.) After making these changes, the classpath is listed in my ActionScript 3 Settings (see Figure 3).

Adding new classpaths to the ActionScript 3.0 Settings dialog box

Figure 3. Adding new classpaths to the ActionScript 3.0 Settings dialog box

Figure 4 shows the (Mac OS) directory where my FLA is saved, so you can see where the _classes directory is located. All of my classes are stored in directories relative to the class package name. So if I have a class named foo.bar.MyClass, my class will be found at _classes/foo/bar/MyClass.as.

Development directory, complete with the FLA file and _classes folder

Figure 4. Development directory, complete with the FLA file and _classes folder

Generally it's considered a best practice to name classes beginning with the most general package name, and then make each subsequent directory more specific. I use com as the most specific class package possible. The directory bigspaceship contains the classes and packages built by/for Big Spaceship, frameworks contains all of the classes or packages that represent fundamental structures we use from project to project, and site contains classes specifically denoted for websites (as opposed to classes used for developing Adobe AIR applications or games). Of course, in this example all of the code is found in _classes/com/bigspaceship/frameworks/site/PreloaderClip.as.

Class package names are arbitrary; you can be as organized or disorganized as you like. When you work in a team environment, however, it's a good idea to adopt standards like this so that everyone on the team knows where to find the PreloaderClip. This ensures that your teammates don't waste energy making their own PreloaderClips. No sense reinventing the wheel, right?

Linkage

In ActionScript 2, linkage was all about identifying a unique ID. In ActionScript 3, linkage is similar but different.

In ActionScript 3, each clip has a unique linkage ID as before. But ActionScript 3 also includes an additional parameter: base class. The base class is whatever type this linkage is. In our case, because we're extending the MovieClip class, it's of the type flash.display.MovieClip. A full discussion of extending classes is beyond the scope of this tutorial, but be sure to check out the ActionScript 3.0 Language and Components Reference for more information on extending classes.

So in our example the preloader is of type com.bigspaceship.frameworks.utils.PreloaderClip. Its base class is flash.display.MovieClip. If I wanted to add the preloader to the Stage dynamically, I could simply add the following code:

import com.bigspaceship.frameworks.utils.PreloaderClip;
var p:PreloaderClip = new PreloaderClip();
addChild(p);

This code creates a new PreloaderClip, which I'll reference via var p. Next I'll add it to the display list via addChild. You may be wondering, "What is the display list?" It's the new way that Flash Player handles objects (movie clips, bitmaps, buttons, etc.). It is rather complex and the full explanation of working with the display list is outside the scope of this article.

Loading at last!

At this point Flash knows that the clip I've placed on the Stage should be treated as a PreloaderClip, my custom class that extends the MovieClip class. I have my Document Root class all set up to leverage the PreloaderClip into displaying the progress. All I have left to do is compile the SWF file and relax.