I came up with this one in Costa Rica, while consistantly waiting three minutes for a 5 meg Flash game (Rebuild, as a matter of fact) to load. The vector graphics take up very little space so over 4 of those megs were just the music. Since I usually play Flash games muted, I figured it was foolish to make players wait until the music loaded before they could start playing. Enter the multi-part Flash preloader.
Preloaders in Flash work by putting some content (the loading animation) on Frame 1 of the base MovieClip, and the rest of the content (the game and music) on Frame 2. The first frame loads in its entirety and any actionscript on that frame executes before the second frame starts to load. This makes sense when you consider that Flash was designed for playing movies. So, can you simply move some of the content (the music) to Frame 3? Yes, it’s that easy!
Well, easy if you’re writing your game on the timeline in the Flash authoring tool. I prefer to use FlashDevelop and the Flex compiler (both free) instead, so my main class is in an AS3 file and the frame metaphor is more obscure. If you start a new project in FlashDevelop using the “AS3 Project with Preloader” template, it creates a Preloader.as class, and a Main.as class with “Always Compile” and the Frame metatag [Frame(factoryClass=”package.Preloader”)]. What this tag does is tell the compiler to put Main.as (and any of its dependancies) on the second frame, and Preloader.as (with its dependancies) on the first frame.
So, if you stack one more class on top…
Music.as with [Frame(factoryClass=”package.Main”)] (set to “Always Compile”)
Main.as with [Frame(factoryClass=”package.Preloader”)]
Preloader.as with a spinny ball animation
The only catch is you can’t determine how much of Main.as has loaded using loaderInfo.bytesTotal because that will also include the bytes from Music.as. Instead you either need to look at Preloader.currentFrame, or try to instanciate Main and catch an error if it’s not ready yet. You could get fancy and use as many frames as you want to determine load order of all your assets, but if you’re dealing with one very large swf it’s still better to cut it up and stream your assets from outside.
While South Korea has plans to put 1 gigabit connections in every home, internet speeds in big countries like the US and Canada have been falling behind. Many areas of the world will be running through 3g cell connections before they even have cable or dsl lines. So go easy on them and don’t forget the preloader! :)
Here’s my example code:
Preloader.as
package loadtest { import flash.display.MovieClip; import flash.events.Event; import flash.events.IOErrorEvent; import flash.utils.getDefinitionByName; public class Preloader extends MovieClip { public function Preloader() { addEventListener(Event.ENTER_FRAME, frameEntered); trace("preloader starting"); // TODO put your spinny ball here but DO NOT reference the Game or Music classes // directly or they will be compiled in to Preloader frame 1 } private function frameEntered (...ig) :void { // the user can begin playing the game if (currentFrame == 2) { trace("frame 2 finished loading, starting game."); var gameClass :Class = getDefinitionByName("loadtest.Game") as Class; addChild(new gameClass()); // TODO remove your spinny ball here because the game has started // finally, the music can also start } else if (currentFrame == 3) { trace("frame 3 finished loading, starting music."); var musicClass :Class = getDefinitionByName("loadtest.Music") as Class; addChild(new musicClass()); // all done loading everything so ditch the preloader stop(); removeEventListener(Event.ENTER_FRAME, frameEntered); } } } }
Game.as
package loadtest { import flash.display.MovieClip; import flash.utils.getDefinitionByName; [Frame(factoryClass="loadtest.Preloader")] public class Game extends MovieClip { public function Game () :void { trace("game starting"); // TODO put your entire game here but DO NOT reference the Music class // directly, or it will be compiled in to Preloader frame 2 try { // this is okay, but will throw an error if the Music isn't loaded yet var musicClass :Class = getDefinitionByName("loadtest.Music") as Class; var music :MovieClip = new musicClass(); } catch (error :ReferenceError) { trace("music class isn't loaded yet"); } } } }
Music.as
package loadtest { import flash.display.MovieClip; /** * This file must be marked as "Always Compile" */ [Frame(factoryClass="loadtest.Game")] public class Music extends MovieClip { public function Music () :void { trace("music starting"); // TODO reference your mp3 or wav classes here so they will be compiled in to // Preloader frame 3. You can also reference Game since it's already loaded. // this is okay var game :Game; } } }