[Note, although this post is right now over a year and a half old, it continues to be one of the most visited pages on my site. If you are just showing up here now, you should note that the data here is a bit dated and may not work exactly as described in the current version of the Flex SDK. In fact, I am pretty sure it DOESN’T work as described anymore. Feel free to read through this for some overall info, but check all the comments as well, which may have more info or links to more up-to-date data. I haven’t had to deal with this situation since I first wrote this, so don’t have the current method for making AS3 preloaders to hand.]
It’s an age old problem. Let’s go back to AS2: You have a relatively large SWF, consisting of exported movie clips in the library, and/or a good deal of code contained in classes. All classes and exported assets load in prior to frame one being displayed, so the viewer sees nothing while it is all loading in.
Solution: Turn off “export first frame” on your exported assets, and place an instance on some frame in the timeline to force them to be compiled into the SWF. For classes, set classes to export to some later frame. Then put your preloader on frame one, stop the timeline, loop until everything is loaded, then jump to an initialization frame.
Now jump to AS3 with mxmlc. There is no timeline. Embedded assets and all classes load in prior to frame one as in a default IDE-created SWF. So, how do you create a preloader?
Well, if you are using Flex, it’s all programmed in there for you, and while I don’t know the specifics, I know you can specify a custom preloader in Flex, and there is obviously a default preloader in a Flex app. Since the Flex framework is all written in AS3, I knew there must be some way to do this in an AS3 only project, but it wasn’t very clear. In fact, you might say it was rather hidden. My first idea was to check the compiler arguments. These two seemed interesting:
frames.frame label class name […]
Specifies a SWF file frame label with a sequence of class names that are linked onto the frame.
This is an advanced option.generate-frame-loader=true|false
Toggles the generation of an IFlexBootstrap-derived loader class.
This is an advanced option.
But that is the entire extent of the documentation on the subject, and doing a web search didn’t turn up a whole lot more.
Then, last Thursday, Ted Patrick happened to be in Boston, and stopped by for a tour of Brightcove. We were sitting there talking to Brian Deitte, who was also part of the Flex team til a few months ago and it occurred to me that one of these guys must have the answer. They pointed me to the [Frame] metadata tag, and said to check out the source for mx.core.Application. Sure enough, I see the following:
/**
- The frameworks must be initialized by SystemManager.
- This factoryClass will be automatically subclassed by any
- MXML applications that don’t explicitly specify a different
- factoryClass.
*/
[Frame(factoryClass=“mx.managers.SystemManager”)]
And, checking out SystemManager, I see a few interesting tidbits:
// NOTE: Minimize the non-Flash classes you import here.
// Any dependencies of SystemManager have to load in frame 1,
// before the preloader, or anything else, can be displayed.
and
- The SystemManager is the first display class created within an application.
- It is responsible for creating an
mx.preloaders.Preloaderthat displays andmx.preloaders.DownloadProgressBarwhile the application finishes loading,- then creates the
mx.core.Applicationinstance.
And finally, the create() method, which is basically called when the movie is fully loaded in:
[as]
public function create(… params):Object
{
var mainClassName:String = info()[“mainClassName”];
if (mainClassName == null)
{
var url:String = loaderInfo.loaderURL;
var dot:int = url.lastIndexOf(".");
var slash:int = url.lastIndexOf("/");
mainClassName = url.substring(slash + 1, dot);
}
var mainClass:Class = Class(getDefinitionByName(mainClassName));
return mainClass ? new mainClass() : null;
}[/as]
A bit more searching around led me to this post by Roger Gonzalez, which explains it a bit more, though highly oriented towards Flex. Even he describes the Frame tag as “semi-secret and bizarre”. 🙂 He also mentions that it is basically an inline alias for the “frames” compiler configuration option. So, with a bit more work I could probably figure out how to do it that way, but the metadata way seems easier so far.
This was enough for me to start testing in AS3 only. Here’s the deal:
First you create your main class as usual, and point the compiler to that as the class to compile.
You then use the [Frame] metadata tag, passing in another class as the factoryClass, like so:
[Frame(factoryClass=“MyFactoryClass”)]
This is where the magic happens. Basically, the Frame tag forces the compiler to create a two-frame SWF. All of your embedded assets, your main class and any classes that are used by the main class are forced into frame two. The only thing that goes into frame one is your specified factory class and any classes or embedded assets that it uses. Hence the above warning to include minimal other classes in this class.
So, this factory class is where you create your preloader. Generally, you want to stop the timeline, and then set up an enterFrame listener, or a timer to check currentFrame against totalFrames. When they are equal, it means that the rest of your classes and assets are loaded in and ready to go. You then want to call nextFrame() to move to frame two so those classes and assets will be available. At that point, you can instantiate your main class, using getDefinitionByName(). This allows your factory class to create a new instance of the main class without compiling a reference to that class into it – which would force the main class and everything else to load in frame one and defeat your preloader.
The slightly weird thing about this setup is that you are usually used to your main class being the “document class”. That is, the main class represents the main timeline of the SWF. In this setup though, the factory class actually becomes the document class. This has a few ramifications:
OK, let’s look at an example. First we have the main class, which we will target with mxmlc:
[as]package {
import flash.display.Sprite;
import flash.display.Bitmap;
[Frame(factoryClass=“com.bit101.MyFactory”)]
public class FrameTest extends Sprite
{
[Embed(source=“big_asset.jpg”)]
private var Asset:Class;
public function FrameTest()
{
init();
}
public function init():void
{
var asset:Bitmap = new Asset();
addChild(asset);
}
}
}[/as]
This doesn’t do much other than embed a large jpeg image, which makes the SWF come in at over 3 MB. If you were to compile this normally, the end viewer would see a blank screen until that jpeg fully loaded in. But the addition of the line:
[Frame(factoryClass=“com.bit101.MyFactory”)]
causes the main class, along with the embedded jpeg, to go into frame two. Note that in this case it is safe for the the constructor to call the init function directly. But, as mentioned earlier, you might want to delay that until the instance is actually added to the display list, to avoid null stage references, etc.
Now let’s look at the factory class:
[as]package com.bit101
{
import flash.display.DisplayObject;
import flash.display.MovieClip;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.utils.getDefinitionByName;
public class MyFactory extends MovieClip
{
public function MyFactory()
{
stop();
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
public function onEnterFrame(event:Event):void
{
graphics.clear();
if(framesLoaded == totalFrames)
{
removeEventListener(Event.ENTER_FRAME, onEnterFrame);
nextFrame();
init();
}
else
{
var percent:Number = root.loaderInfo.bytesLoaded / root.loaderInfo.bytesTotal;
graphics.beginFill(0);
graphics.drawRect(0, stage.stageHeight / 2 – 10,
stage.stageWidth * percent, 20);
graphics.endFill();
}
}
private function init():void
{
var mainClass:Class = Class(getDefinitionByName(“FrameTest”));
if(mainClass)
{
var app:Object = new mainClass();
addChild(app as DisplayObject);
}
}
}
}[/as]
Note that it extends MovieClip. It immediately stops the timeline and adds an enterFrame listener. In that handler, it checks to see if framesLoaded == totalFrames. If so, it calls nextFrame() to make the main class available, and then calls init, which gets a reference to the main class, instantiates it, and adds it to the display list.
While second frame is still loading, it simply draws a progress bar on the stage, using root.loaderInfo.bytesLoaded and root.loaderInfo.bytesTotal to access the percent loaded.
This is a pretty simple example, but shows you how it works. I’m pretty sure it’s the only documentation out there on how to do this in AS3, so feel free to share it around.