Extracting class assets from an external SWF at runtime.

I find one of the single most powerful (and unbelievably cool!) new features in AS3 to be the ability to extract classes at runtime from an externally loaded SWF movie. I was really excited to take advantage of the feature while implementing the Lassie engine‘s new library system, which works by dynamically pulling classes out of an externally loaded SWF library and then composing them into a game layout on the fly. Now this process can be a bit cumbersome without helper methods; and in the case of a large dynamic asset management framework like Lassie, I’ve found it most convenient to just to standardize the SWF library publishing method for quick, easy, and reliable class accessibility.

So, the goal here is to create standardized SWF packaging with easily extractable class assets. That packaging should allows us to quickly read the class contents of the SWF and then to grab them out without hassle. As such, the logical place to start here is with an interface. So, here it is:

*Save as: com/lassie/lib/IMediaLibrary.as

package com.lassie.lib
{
    public interface IMediaLibrary
    {
        function get contents():Array;
        function getAsset(className:String):*;
    }
}

That interface gives us the ability to get “contents” which will return an array of class names that are contained within the library. We can then feed those class names (listed in the contents array) into the “getAsset()” method to retrieve an instance of that class. Now for the actual MediaLibrary class, which gets assigned as a document class to the library FLA document.

*Save as: com/lassie/lib/MediaLibrary.as

package com.lassie.lib
{
    import flash.display.MovieClip;

    public dynamic class MediaLibrary extends MovieClip implements IMediaLibrary
    {
        public function MediaLibrary():void
        {
            super();
        }

    // --------------------------------------------------
    //  Include a library media class
    // --------------------------------------------------

        public function addClass($class:Class, $path:String=""):void
        {
            if (($path.split(".")).length > 1)
            {
                this[$path] = $class;
            }
            else
            {
                var $cname:String = $class.toString();
                this[$cname.substr(7, $cname.length-8)] = $class;
            }
        }

    // --------------------------------------------------
    //  Get library contents (lists class names)
    // --------------------------------------------------

        public function get contents():Array
        {
            var classList:Array = new Array();
            for (var j:String in this)
            {
                classList.push(j);
            }
            classList.sort();
            return classList;
        }

    // --------------------------------------------------
    //  Extract library class asset
    // --------------------------------------------------

        public function getAsset(className:String):*
        {
            try
            {
                var classObj:Class = this[className];
                return new classObj();
            }
            catch(e:Error)
            {
                trace("error extracting requested class.");
            }
            return null;
        }
    }
}

That document class implements the two interface methods, plus gives us one additional feature: to add classes to the library. Classes are added with the “addClass()” method, which takes a reference to the class object and an optional string of the class’ fully qualified path. You can add classes from the first-frame timeline actions of the library FLA’s root timeline. To add a class:

addClass(com.project.MyMovieClip, "com.project.MyMovieClip");

Or if the class is unpackaged and has no class path, you can add it by direct reference (the class’ “toString()” value is logged as the path):

addClass(MyMovieClip);

Assuming that your MediaLibrary document publishes without errors, you can load it into another movie, get the MediaLibrary’s list of contents, and then start requesting those assets out of it with ease. The load/retrieval script goes something like this:

var libLoader:Loader = new Loader();
// ... load your library SWF into the current movie ...
var lib:IMediaLibrary = libLoader.content as IMediaLibrary;
var mc:MovieClip = lib.getAsset(lib.contents[0]) as MovieClip;

Fun!

Advertisements

17 comments so far

  1. fatbuoy1 on

    So.. in (very) plain English.. what can this be used for?

  2. bigmac on

    Simplest words: load in a SWF and pull s**t out of it. Actual runtime library sharing.

    While this might seem simple, you couldn’t do it before with AS2. Whenever you loaded in a SWF movie, it went direct to stage. You couldn’t create instances of a loaded SWF movie or any of its contents. An obvious example of this can be seen in AS2 if you load in an external SWF movie and then try to display a second instance of it using duplicateMovieClip(). It doesn’t work… an AS2 SWF can only work with assets that are contained within its own library. If you wanted two instances of a single external AS2 SWF movie, you’d have to load the same file twice.

    So, AS3 is fantastic. It lets us load in a SWF movie and then start pulling stuff out of its library. Where before an external SWF was just a means to display additional content, now we can use an external SWF as a box that just holds serialized Flash data. We can then pull stuff out of that box and do whatever we want with it, whenever we need it. It blows the possibilities of a dynamic content framework wide open.

    A good conceptual metaphor here would be to think of a toy action figure. You know the ones I mean… they come in boxes with a transparent front on them so you can look inside at the cool hero figure and all the cool accessories that he comes with. Well, AS2 would let you load in that box, but would force you to be a good collector and never open the packaging. You could only ever look in at your cool toy through the front window. AS3 lets you open it up and get the stuff out; it even lets you pick and choose. Lets say you only want one of your hero’s accessories, say, his sword. No prob: reach in a just grab that little piece. Let’s say you want to mix and match… again, no problem. Just load in a ninja turtle box and a justice league box, and then switch Donatello’s staff with Captain America’s shield. Why not? Action figures can use each others’ accessories in real life.

    If you want more info on the possibilities of a media library system, check out the shepherd tutorial that I wrote, at: http://shepherd.gmacwill.com/documentation/media_libraries/

  3. fatbuoy1 on

    hah I love your analogies! Makes sense tho, and yeh, that DOES sound cool :)

  4. Nisim Joseph on

    10x (thanks) on this great way of working with library. i will use it be sure about it.

  5. Nisim Joseph on

    i took your idea and develop it some more. i will use it in my company project and it just what i was needed. i can make library of Assets, Classes and much more with this simple way.

    thank you on this!!!

  6. roznet on

    Thanks a lot. This is something i have been trying to do and your explanation was most helpful to me.

    One issue I struggled for a while was a coercion error when loading the external movie. For me coercing to the interface name failed, but coercing to a MovieClip worked and I was able to use the method getAsset. I don’t understand the logic.I am using FlashCS4.
    Here is what my code look like:

    var url:URLRequest = new URLRequest( “http://localhost/flash_dev/bin/da_arrows.swf” );
    var libLoader:Loader = new Loader();
    libLoader.contentLoaderInfo.addEventListener( Event.COMPLETE, completeHandler );
    libLoader.load( url );

    function completeHandler( e:Event ){
    // I’d expect this to work:
    //var lib:IDynamicAssetLibrary = IDynamicAssetLibrary( libLoader.content );
    // But you get:
    //Error #1034: Type Coercion failed: cannot convert lib.dynassets::DynamicAssetLibrary@10c05601
    // to lib.dynassets.IDynamicAssetLibrary

    //while the below worked:
    var lib:MovieClip = MovieClip( libLoader.content );
    trace(lib); // Output is: [object DynamicAssetLibrary]
    var mc:MovieClip = lib.getAsset(“ArrowLeft”);
    addChild( mc );
    };

    • bigmac on

      I’ve found a few common pitfalls which lead to that coercion error with this setup. Check over your configuration and see if any of these apply:

      1) Make sure that your parent movie imports the same interface with the same class path. Looking at your code, it looks like you’re doing this correctly.

      2) Make sure that your parent and child movies are all compiled with the same version of the interface. It’s common to compile your external asset bundles and then to forget about them. So, just remember to recompile all movies whenever you make a change to the interface.

      3) I’m not sure why, but sometimes FlashPlayer doesn’t seem to like the wrapper method [ “IInterface(someObject)” ] of transcending data types. If you ever have a problem with a wrapper, try using the “as” transcender [ “someObject as IInterface” ]

      hope that helps!

  7. Geof on

    Thanks a bundle for that info. Works a treat

  8. […] Extracting class assets from an external SWF at runtime. « Flash Game Development (tags: flash actionscript as3) […]

  9. pendar on

    Hmm, I keep getting an error (been trying to fix it for the past 3 hours with no luck!)

    I have IMediaLibrary.as and MediaLibrary.as exactly as you have here. I have an Assets.FLA file whose document class is set to MediaLibrary.as. In that file, I have 2 library symbols called Knob and Track. They are simple vector shapes, and they are both “exported to actionscript” with the class name Knob and Track.

    Now in my main script, I try to run this:

    var loader:Loader = new Loader();
    loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
    loader.load(new URLRequest(‘com/kibboko/assets/Assets.swf’));

    protected function onLoadComplete(e:Event):void{

    var lib:MediaLibrary = e.target.content as MediaLibrary;
    trace(lib.contents);
    var mc:MovieClip = lib.getAsset(lib.contents[0]) as MovieClip;
    addChild(mc);

    }

    The result of the trace(lib.contents) seems to be correct, it spits out “Knob, Track”. But the next line gives me this error:
    error extracting requested class.

    Am I doing something wrong? Thanks for your article and your help.

    • bigmac on

      You’re getting a message from the MediaLibrary, not an error from the Flash runtime (which is a start). You’ll get the “error extracting requested class.” when the MediaLibrary can’t resolve the provided key as a valid class asset. Without playing with your setup, it’s difficult to guess as to what (specifically) is wrong.

  10. Anthony on

    thanks lot for the MediaLibrary class, worked like a charm

    does it work for components like fl.controls.Slider ?

    tried, but could no make it work.

    • bigmac on

      That same architecture would work if you expanded it to. I only have it set up to receive and return MovieClips. For controls like that’s you’d probably need to build a special set of methods specifically designed for the media type.

  11. Wolfgang on

    Hello, i am from Germany and newbe! I find your site i that is totally great. Load content found of the library from a extern SWF. Extract what i want do. Like: A banner with some picture running in circle and meanwhile i want load a extern SWF and get more picture out of this extern SWF. So load a small SWF first and during it run load in background more stuff…! Perfect! The only fault is in have not the abilities/experience to make it real!! Anybody have a help…!?

    But i see here is the solution!

    Please, thousand times please have a look in my simple code and show me how the way to add picture (like that: http://www.888biz.asia/tmp/capture.png) from the external SWF library in my mail SWF!

    Here the script:

    function run_loop(){

    var n:int =5;
    var i:Number = 1+ Math.floor(Math.random()*n);
    var runnum;

    var _swfLoader:Loader;
    var _swfContent:MovieClip;

    loadSWF(“contain2.swf”);

    function loadSWF(path:String):void {
    var _req:URLRequest = new URLRequest();
    _req.url = path;

    _swfLoader = new Loader();
    _swfLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, addSWF);
    _swfLoader.load(_req);}

    function addSWF(event:Event):void {
    event.target.removeEventListener(Event.INIT, addSWF);

    _swfContent = event.target.content;
    addChild(_swfContent);
    _swfContent.eckstein.alpha = 1;

    var lib:IMediaLibrary = _swfLoader.content as IMediaLibrary; /// get Error Typ not found or no compile constante…
    var mc:MovieClip = lib.getAsset(lib.contents[0]) as MovieClip;}

    switch(i)
    {
    case 1:
    runnum=pic_mc02;
    break;
    case 2:
    runnum=mc; // here should the picture from the external SWF
    break;
    case 3:
    runnum=pic_mc03;
    break;
    default:
    runnum=pic_mc04;
    }

    var myImage:BitmapData = new runnum(1, 1);
    var bitmap:Bitmap = new Bitmap(myImage)

    bitmap.alpha =0;
    bitmap.x =0;
    bitmap.y =0;

    addChild(bitmap);

    TweenLite.to(bitmap, 2, {alpha:1, ease:Strong.easeIn, delay:2, onComplete:nachoben});

    function nachoben(){
    run_loop();}}
    run_loop();

    • bigmac on

      Wolfgang –

      Sounds like you’re using a sledge hammer to pound in a nail… if all you’re trying to do is load an image, then this article’s described methodology of creating an external SWF library with extractable assets is overkill. To load an external image, all you need is this:

      var $loader:Loader = new Loader();
      $loader.load( new URLRequest(“myimage.jpg”) );
      addChild($loader);

  12. Christian Snodgrass on

    Great article.

    I am having one issue though:
    get content is returning an empty array for me.

    The media library has nothing on the stage, but everything in the library (with the ones that exporting properly set up).

    I then load it up and everything. I give it a try and get content returns an empty array and getAsset throws an error and returns null.

    I know I’m loading it properly because it gives me the proper class, and I can even use describeType to get the proper info.

    Any ideas?

    Thanks.

    • bigmac on

      Christian,

      I’d suggest downloading my game engine framework, Lassie Shepherd (available at http://lassiegames.com/lassie/download/lassie-shepherd/). Unzip that and look over the included resources. There is a base class provided for making this type of media library, and an extensive PDF tutorial on configuring a Flash movie as a media library. Many people have gotten up and running with this library system using those resources.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: