Feb
28
2009

Two ways of preloading in ActionScript 3

Preloader is one of these things that are essential when building a Flash project that will be published on the internet. Firstly, it prevents from playing before all its content is loaded. Without it, animation could start without somactioe of the movie clips on the scene. Secondly, it informs users how long they need to wait before downloading is complete, so they either can close the browser and search for some other content that loads faster (the worst case scenario Laughing) or they can wait few more seconds to experience your great work. There are two ways of preloading your content, you can create "internal" preloader for a movie clip itself (should I call that selfpreloader?) or you can create one swf file - loader that will load another swf file - your main project.

 

In both approaches you need to access LoaderInfo object either through loaderInfo property when preloading the clip itself or contentLoaderInfo when loading external content using Loader object. The diagram below from Adobe illustrates this:

loaderInfo diagram

Self preloading

This method would suite you when you don't want to have any external files, you just want to have one swf file (this is usually the case when creating the Flash games as most of the websites hosting them require just one file). Although you may find many preloader tutorials on the internet, not all of them mention that this way of preloading work correctly only if you won't forget to "tell" Flash to keep your all assets like images, movie clips, sound etc away from the first frame. Also not all of them say about the bug in Internet Explorer that will prevent dispatching COMPLETE event. I will explain that further in a minute.

First, let's create a new project and import some massive bitmap file (in terms of MBs) on the stage. The bigger image then better as it would be easier to test your preloader progress. Save the project and create a new ActionScript file. Save it as a InternalPreloader.as file in the same folder where the fla file is (your main project).

We need to import two types of events: ProgressEvent and Event. The whole thing about the proloading is to add event listeners to the loaderInfo property. ProgressEvent.PROGRESS will be dispatched as soon as the loading starts and will carry on information about number of bytes already loaded and total number of bytes that needs to be loaded. OnLoading function will be continually called during the downloading process. To get percantage loaded we divide current number of bytes loaded and total number of bytes and multiply by 100. Next we use Math.floor to make it integer value and convert to String so it can be printed by the textField. This will update user with the current loading status. Event.COMPLETE will indicate that the process is complete and we now can start the movie and remove preloader textField with event listeners that won't be used anymore.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package
{
import flash.display.MovieClip;
import flash.events.ProgressEvent;
import flash.events.Event;
import flash.text.TextField;

public class InternalPreloader extends MovieClip
{
//create a text field to show the progress
var progress_txt:TextField = new TextField();
function InternalPreloader():void
{
//stop the timeline, will play when fully loaded
stop();
//position text field on the centre of the stage
progress_txt.x = stage.stageWidth / 2;
progress_txt.y = stage.stageHeight / 2;
addChild(progress_txt);

//add all the necessary listeners
loaderInfo.addEventListener(ProgressEvent.PROGRESS, onProgress);
loaderInfo.addEventListener(Event.COMPLETE, onComplete);
}

function onProgress(e:ProgressEvent):void
{
//update text field with the current progress
progress_txt.text = String(Math.floor((e.bytesLoaded/e.bytesTotal)*100));
}

function onComplete(e:Event):void
{
trace("Fully loaded, starting the movie.");
//removing unnecessary listeners
loaderInfo.removeEventListener(ProgressEvent.PROGRESS, onProgress);
loaderInfo.removeEventListener(Event.COMPLETE, onComplete);
//go to the second frame.
//You can also add nextFrame or just play()
//if you have more than one frame to show (full animation)
gotoAndStop(2);
}
}
}

Once you save the ActionScript code, go back to the main project and in Properties panel, type the name of your document class "InternalPreloader" to the Class text box, as on the picture below:

document class

Now, how to test the preloading process? You can either upload it on your webserver or even better use Flash Simulating Download feature. When trying this on your webserver, your internet connection may be too fast (unless you have placed 20 MB bitmap on the stage or you're still on the modem). Also your browser (and/or proxy server) may cache your swf file, so you wouldn't be able to test it again until you clean your cache. This is why it's better to test preloader from Flash at the developing phase and finally, when project is done on the webserver. To use Simulated Download feature, test movie as usually, by pressing CTRL + Enter. When player window pops up, select View and Download Settings and DSL - this should be enough for a test. Now hit CTRL + Enter again to see the effect.

download settings

Movie starts after a while, but you don't see any progress information, only traced output message "Fully loaded, starting Movie". The reason is that Flash first loads all the assets from the stage and library and then launches the script with the code (and our preloader). To avoid that, go back to your project, go to the timeline and move the image object from the first frame to the second. Now test the movie again. You should see a text field in the middle of the stage indicating loading progress and after that the actual image.

So far so good, let's do another test, this time leaving a timeline empty. Remove the second frame from the timeline and the image, leave the first frame empty. Create new Movie Clip (F8) and drag and drop into it your image from the library. Now you should see new movie clip in the library - containing the image. Right click on it, choose properties, select Export for ActionScript and give it a class name "Image".

Class name

Now we are going to add this image on the stage from the ActionScript level. I modified our code by adding a line to create a new variable of type Image and adding it on the stage when the whole movie is loaded

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package
{
import flash.display.MovieClip;
import flash.events.ProgressEvent;
import flash.events.Event;
import flash.text.TextField;

public class InternalPreloader extends MovieClip
{
var progress_txt:TextField = new TextField();

function InternalPreloader()
{
stop();

progress_txt.x = stage.stageWidth / 2;
progress_txt.y = stage.stageHeight / 2;
addChild(progress_txt);

loaderInfo.addEventListener(ProgressEvent.PROGRESS, onProgress);
loaderInfo.addEventListener(Event.COMPLETE, onComplete);
}

function onProgress(e:ProgressEvent):void
{
progress_txt.text = String(Math.floor((e.bytesLoaded/e.bytesTotal)*100));
}

function onComplete(e:Event):void
{
trace("Fully loaded, starting Movie");
gotoAndStop(2);
//now when movie is loaded, we can instantiate the image and add it on the stage
var image_mc:Image = new Image();
addChild(image_mc);
//removing stuff we won't use anymore
loaderInfo.removeEventListener(ProgressEvent.PROGRESS, onProgress);
loaderInfo.removeEventListener(Event.COMPLETE, onComplete);
removeChild(progress_txt);
}
}
}

 

Now when you test the movie, you will notice that again preloader doesn't show the progress. Although the first frame on the timeline is completely empty, Flash loads all library assets (sprites, movie clips, sound objects, everything what you imported or created there) along with the first frame. So again, you need to let Flash know that you want to import all this things at the second frame. To do that, go to File / Publish Settings, select Flash tab, click on Settings button, next to the Script drop down:

Publish settings

Now change Export classes in frame value from 1 to 2 as on the picture below:

Export in frame

Note that, after you set Flash to export your assets in frame number two, you won't be able to instantiate any of the classes unless you are actually in frame number 2 or further. Flash DOESN'T know about any of the library objects in frame one.

That is why in the code I first move to the second frame and then instantiate the image object. You also need to create the second frame on the timeline (if you still have only one frame), otherwise Flash won't be able to move there.

To ensure that everything is imported in the second frame, you can use Bandwidth profiler that presents the amount of data loaded at each frame. To enable it, press CTRL+B when testing the movie. Screenshot below shows that one MB image is loaded at the second frame (each column represents data on each frame).

Bandwidth Profiler

There is also another important thing to remember, your movie may not work properly in Internet Explorer 6 if Flash Player 9 is installed (works fine on FF / Safari etc). There is a problem with dispatching Event.COMPLETE event. This bug has been fixed in never versions of Flash Player (>9). But you never know what version may be used by your users, maybe they haven't updated player to the lasted version? What would happen in this case is that the movie will never start, it will finish with percentage loaded 100% and then stop doing nothing else. The workaround is to add an event listener for ENTER_FRAME and check for condition bytes loaded = total bytes (instead of waiting on COMPLETE event).

addEventListener(Event.ENTER_FRAME, waitOnComplete)

 

function waitOnComplete(e:Event):void
{
if(loaderInfo.bytesLoaded == loaderInfo.bytesTotal)
{
removeEventListener(Event.ENTER_FRAME, waitOnComplete)
}
}

 

Loading external files

This is probably a preferred method for most of the developers as you don't have to worry about headaches like exporting any assets to other frames. In this apporach, two separate Flash projects are required, your actual movie and a preloader. Remember that a preloader needs to be as small as possible, so you shouldn't add any bitmaps or sounds into it (otherwise you will need to create a preloader for your preloader...). The preloader file will load your swf file and once this is complete, will start it. You can use the same method of preloading to load other supported files like JPG, MP3, FLV.

First create a new Flash project, import the same big bitmap on the stage, save it as mainMovie.fla and publish it to SWF file. Next create a new ActionScript file, save as ExternalLoader.as and use the code below. It is quite similar to the code from internal method, this time we use Loader object to get the external content and use currentLoaderInfo to get information about progress (refer to the diagram from beginning of this article).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package
{
import flash.display.MovieClip;
import flash.net.URLRequest;
import flash.display.Loader;
import flash.events.ProgressEvent;
import flash.events.Event;
import flash.text.TextField;

public class ExternalLoader extends MovieClip
{
//text fiels to show the progress
var progress_txt:TextField = new TextField();
//loader object will load external swf file
var myLoader:Loader = new Loader();;

function ExternalLoader():void
{
//OPEN fires when loading process starts
myLoader.contentLoaderInfo.addEventListener(Event.OPEN, onOpen);
//PROGRESS carries info about number of bytes loaded
myLoader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onProgress);
//COMPLETE fires when process finishes
myLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
//start loading of mainMovie.swf
myLoader.load(new URLRequest("mainMovie.swf"));
}

//loading started so add the text field on the stage
function onOpen(e:Event):void
{
progress_txt.x = stage.stageWidth / 2;
progress_txt.y = stage.stageHeight / 2;
addChild(progress_txt);
}

//update progress
function onProgress(e:ProgressEvent):void
{
progress_txt.text = String(Math.floor((e.bytesLoaded/e.bytesTotal)*100));
}

//end of loading so clean up and add loaded clip on the stage
function onComplete(e:Event):void
{
trace("Loading process complete");
//remove unnecessary listeners
myLoader.contentLoaderInfo.removeEventListener(ProgressEvent.PROGRESS, onProgress);
myLoader.contentLoaderInfo.removeEventListener(Event.OPEN, onOpen);
myLoader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onComplete);
//remove text field
removeChild(progress_txt);
//add loaded object
addChild(myLoader.content);
}
}
}

Next create the new Flash project again, add ExternalLoader.as as the Document class and save it as externalLoader.fla.

Document Class

Now test the movie using simulating download feature (as before with internal loader example). Your externalLoader.swf should load mainMovie.swf indicating the progress and at the end it will play it. Happy days.

Sometimes you may need to have more control when the loaded movie starts to play, for example when you load more external files and want to play them all together at the same time (when the last assed is loaded). This may be the case when creating games - you may have external swf file with the game and mp3 file with the background music and you want to hold on with starting your game until the music is loaded. To do that, you can just control their timelines, by using play() from the loader object. You can create movieClip object myLoadedMovieClip (or you can use just myLoader.play()) and then within the onComplete method replace addChild(myLoader) with the lines below. Note that casting is necessary when assigning the reference of loader content to the MovieClip object. And of course you should stop the loaded movieClip (in our case mainMovie.swf) timeline by adding stop() in it's first frame.

//add loaded object
myLoadedMovieClip = MovieClip(myLoader.content);
myLoadedMovieClip.play();
addChild(myLoadedMovieClip);

The other method of controling when loaded movies are played is to listen for the Event.ADDED_TO_STAGE event from the loaded movies side. This event is fired every time when a movie clip is added to the stage. In our example loaded mainMovie.swf will receive that event when the exteralLoader.swf will execute addChild(myLoader.content). This is the code that you use in our mainMovie.swf.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package
{
import flash.display.MovieClip;
import flash.events.Event;

public class MainMovie extends MovieClip
{
public function MainMovie()
{
stop();
//listening if clip is added to a stage
addEventListener(Event.ADDED_TO_STAGE, addedToTheStage);
}

//added to a stage so play it
public function addedToTheStage(e:Event):void
{
play();
removeEventListener(Event.ADDED_TO_STAGE, addedToTheStage);
}

}
}

 

Improving preloading experience

There are several ways of graphically improving either internal or external preloader. Text field showing just percentage of a movie loaded is informative enough but sure not the sexiest thing you can do. There are many good tutorials with great ideas and examples.

Popular way of representing progress is to use a progress bar. You can do that by creating a movie clip of a rectangle representing 100% load. Instead of passing a progress as a string to a text field, you can scale your rectangle using a line below. Remember to register your movie clip to the left side, otherwise your movie will grow in different directions than right.

Registration of progress bar

progressBar_mc.scaleX = e.bytesLoaded/e.bytesTotal;

You can also use ProgressBar that is one of the Flash components.

The other way, that gives you fancier options, is to create animated movie clip 100 frames long. Every new percentage loaded will be represented by different a frame.

progressBar_mc.gotoAndStop(Math.floor((e.bytesLoaded/e.bytesTotal)*100));

There are much more ways of doing your preloader, imagination is the only limit :). If you lack of the ideas, browse internet as there are many tones of tutorials on how to improve your standard preloader.

Links

There is a nice tutorial by Jeff Fulton on creating preloaders for games on 8bit rocket : http://www.8bitrocket.com/newsdisplay.aspx?newspage=10807

Comments 

 
+1 #7 lock 2012-01-08 18:13
For the Loading External files, how would you pass the loaderInfo from the preloader to the main swf?
Quote
 
 
+1 #6 Phantom 2011-05-04 12:05
Hi,

Thats a great example, just there is one issue. What to do when flash is not getting bytesTotal?
Quote
 
 
0 #5 Hasan 2010-12-28 16:48
Quoting sigman:
If you want to preload everytime when loading different external files, create function to reuse that piece of code when you have URLRequest and call myLoader.load(), as below:

function loadAnotherFile (path:string):v oid
{
myLoader.contentLoaderIn fo.addEventListene r(Event.OPEN, onOpen);
myLoader.contentLoaderIn fo.addEventListene r(ProgressEvent .PROGRESS, onProgress);
myLoader.contentLoaderIn fo.addEventListene r(Event.COMPLETE, onComplete);
myLoader.load(new URLRequest(path ));
}

Then you can call this function as below: loadAnotherFile ("yourExternalFil e.swf");


:lol: superb!!
Quote
 
 
-1 #4 Dave 2010-04-14 17:36
@Ben, I'm also curious on how to use this code so that it works with every external swf I call onto the main stage and not simply the main.swf. Also, Ben, I had the same issue and what I did was leave the first keyframe empty on the main timeline of my main swf file. Do exactly know why, but it worked.
Quote
 
 
0 #3 sigman 2010-04-06 17:51
If you want to preload everytime when loading different external files, create function to reuse that piece of code when you have URLRequest and call myLoader.load(), as below:

function loadAnotherFile (path:string):v oid
{
myLoader.contentLoaderIn fo.addEventListene r(Event.OPEN, onOpen);
myLoader.contentLoaderIn fo.addEventListene r(ProgressEvent .PROGRESS, onProgress);
myLoader.contentLoaderIn fo.addEventListene r(Event.COMPLETE, onComplete);
myLoader.load(new URLRequest(path ));
}

Then you can call this function as below: loadAnotherFile ("yourExternalFil e.swf");
Quote
 
 
0 #2 sigman 2010-04-06 17:46
Hi, first thing I would check is if the code for buttons is placed before the frame where the buttons are placed. In other words, if you want to assign action script code to any of your movie clips or buttons, you need to make sure that these elements are at the same frame or before the frame with code. Otherwise Flash won't know that these elements exist.
Quote
 
 
0 #1 Ben 2010-04-05 14:44
Hello,
I just tried your script on "Loading External Files" and all works beautifully except that once my 4-frame main swf is loaded, none of the buttons work. Also, I am a bit unclear how to call the preloader everytime I want to load an external swf into the main swf.

Suggestions?

thanks.
Quote
 

AS3 Tips

Follow me on Twitter