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!
September 8, 2008 at 1:15 pm |
So.. in (very) plain English.. what can this be used for?
September 8, 2008 at 6:42 pm |
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/
September 9, 2008 at 8:47 am |
hah I love your analogies! Makes sense tho, and yeh, that DOES sound cool :)
September 28, 2008 at 7:45 pm |
10x (thanks) on this great way of working with library. i will use it be sure about it.
September 30, 2008 at 8:38 pm |
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!!!
December 30, 2008 at 10:42 am |
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 );
};
January 1, 2009 at 5:50 pm |
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!
January 9, 2009 at 10:36 am |
Thanks a bundle for that info. Works a treat
February 14, 2009 at 12:30 pm |
[...] Extracting class assets from an external SWF at runtime. « Flash Game Development (tags: flash actionscript as3) [...]
March 23, 2009 at 10:25 pm |
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.
March 24, 2009 at 5:26 pm |
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.
June 12, 2009 at 5:55 am |
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.
June 29, 2009 at 9:19 pm |
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.