Archive for the 'Guttershark' Category
Font Libraries with Guttershark
Another key thing when working with Flash is fonts. It can be somewhat intimidating as the documentation for using font library symbols doesn’t make anything obvious. It takes a bit of reading and messing around to get it right.
If you do some snooping around on google, there are quite a few articles that outline the basics of using embedded fonts, or font libraries. I’ll take you beyond what the basics are, and show you how to integrate font libraries with guttershark.
First things first, let’s look at the model file for loading fonts:
<model> <assets> <asset libraryName="fonts" src="fonts.swf" preload="true" /> </assets> <fonts> <font libraryName="LucidaGrandeBold" /> <font libraryName="LucidaGrandeRegular" /> </fonts> </model>
This model file defines an asset to load, which is a swf that contains our fonts. And it declares two fonts that need to be registered with Flash’s Font.registerFont method.
Now let’s look at the first pass of a document controller:
package { import flash.text.TextFormat; import flash.text.TextField; import flash.events.Event; import net.guttershark.control.PreloadController; import net.guttershark.control.DocumentController; public class Main extends DocumentController { override protected function flashvarsForStandalone():Object { return {model:"model.xml"}; } override protected function setupComplete():void { pc=new PreloadController(); pc.addItems(ml.getAssetsForPreload()); pc.addEventListener(Event.COMPLETE,onComplete); pc.start(); } private function onComplete(e:Event):void { //this registers every font that is declared in model XML. ml.registerFonts(); } } }
This document controller set’s up the bare minimum to get fonts loaded in and registered. From there, we’re ready to start using embedded fonts on text fields with text formats, or stylesheets.
Before we do that, let’s take a look at some some key methods that guttershark has for getting and setting stylesheets or text formats, and i’ll explain what they do internally.
Two key methods:
- Model#getTextFormatById() - Model#getStyleSheetById()
The most important thing that these two methods do is automatically resolve fonts to their “proper name”. A proper name is the name of a font instance - the “fontName” property of a Font instance.
Here’s an addition to the model for a textformat:
... <textformats> <textformat id="labelText" font="LucidaGrandeBold" size="14" /> </textformats> ...
In this example, the “font=LucidaGrandeBold” indicates that the font to use is the font “LucidaGrandeBold” which in turn is the “export for actionscript” name in the fonts swf. Internally, the getTextFormatById method retrieves an instance of this font object, and assigns the “font” property of a textformat with “font.fontName”.
Here’s some psuedo code to show what this method does:
function getTextFormatById(id:String):TextFormat { finalTextFormat = new TextFormat(); tfNode = getXMLNodeByID; fontName = tfNode.@font; fontClass = getFontFromLibrary(fontName); Font.registerFont(fontClass); fontInstance = fontClass(); finaltextFormat.font = fontInstance.fontName; //KEY //apply other node attribtues to textFormat (bold, size, etc) return finalTextFormat; }
Simple right? Now let’s make an addition in the model for stylesheets:
... <stylesheets> <stylesheet id="example"> <![CDATA[ .body{ font:LucidaGrandeRegular; fontSize:10; } ]]> </stylesheet> </stylesheets> ...
Gutteshark supports a custom css property “font”, which indicates to guttershark that it should look up that font, and get the right “proper name” for that font, and use it as the “fontFamily” property.
Here’s some psuedo code for this method:
function getStyleSheetById(id:String):StyleSheet { styleSheet = getStyleSheetNodeFromXML; styleSheet = CSS.parseCSS(styleSheet.toString()); styleNames = styleSheet.styleNames; var finalFontFamily:String; l = styleNames.length; i = 0; for(i;i<l;i++) { style = styleSheet.getStyle(styleNames[i]) for(key in style) { if(key=="font") { fontClass = AssetManager.gi().getClass(style[key]); Font.registerFont(fontClass); fontInstance = fontClass(); finalFontFamily = fontInstance.fontName; delete style[key]; } } if(finalFontFamily) style['fontFamily'] = finalFontFamily; //KEY styleSheet.setStyle(styleNames[i],style); //update final style } return styleSheet; }
Fairly simple right?
The benefit of all this is that you no longer need to write exhaustive code for every text field, and with the help of some shortcuts, we can setup up embedded text fields into a few lines of code.
Here’s an example of the work required for setting up text formats for each text field, without the help of guttershark:
var fc:FontClass = AssetManager.gi().getClass("MyFontLibraryName"); Font.registerFont(fc); var fi:Font = fc(); var tf:TextFormat = new TextFormat(); tf.font = fi.fontName; //set other textFormat properties var tfield:TextField = myTFOnTheStage; tfield.embedFonts = true; tfield.defaultTextFormat = tf; tfield.text = "Hello World";
Here’s an example with guttershark’s help:
var tf:TextFormat = ml.getTextFormatById("example"); var tfield:TextField = myTextFieldOnTheStage; utils.text.textFormat(tfield,tf); tfield.text = "Hello World";
Much cleaner.
Ok, so let’s put some of this together with a real example. Here’s the final model xml file:
<?xml version="1.0" encoding="utf-8"?> <model> <assets> <asset libraryName="fonts" src="fonts.swf" preload="true" /> </assets> <fonts> <font libraryName="LucidaGrandeBold" /> <font libraryName="LucidaGrandeRegular" /> </fonts> <textformats> <textformat id="labelText" font="LucidaGrandeBold" size="14" /> <textformat id="lucidaGBold10" font="LucidaGrandeBold" size="10" bold="true" /> <textformat id="lucidaGBold12" font="LucidaGrandeBold" size="12" bold="true" /> <textformat id="lucidaGRegular10" font="LucidaGrandeRegular" size="10" /> <textformat id="lucidaGRegular12" font="LucidaGrandeRegular" size="12" /> </textformats> <stylesheets> <stylesheet id="example"> <![CDATA[ .body{ font:LucidaGrandeRegular; fontSize:10; } ]]> </stylesheet> <stylesheet id="example2"> <![CDATA[ .body{ font:LucidaGrandeRegular; fontSize:10; } .bold{ font:LucidaGrandeBold; fontWeight:bold; fontSize:14; color:#FF0066; } ]]> </stylesheet> </stylesheets> </model>
And here’s the final document controller:
package { import flash.text.TextFormat; import flash.text.TextField; import flash.events.Event; import net.guttershark.control.PreloadController; import net.guttershark.control.DocumentController; public class Main extends DocumentController { //bunch of fields on stage.. public var stylesheetsLabel:TextField; public var textFormatsLabel:TextField; public var txf1:TextField; public var txf2:TextField; public var txf3:TextField; public var txf4:TextField; public var txf5:TextField; public var txf6:TextField; public var txf7:TextField; public var ss1:TextField; public var ss2:TextField; public var ss3:TextField; public var ss4:TextField; public var ss5:TextField; public var ss6:TextField; public var ss7:TextField; override protected function flashvarsForStandalone():Object { return {model:"model.xml"}; } override protected function setupComplete():void { pc=new PreloadController(); pc.addItems(ml.getAssetsForPreload()); pc.addEventListener(Event.COMPLETE,onComplete); pc.start(); } private function onComplete(e:Event):void { //this registers every font that is declared in XML. ml.registerFonts(); //set up the labels. var labelTF:TextFormat = ml.getTextFormatById("labelText"); stylesheetsLabel.embedFonts=true; stylesheetsLabel.setTextFormat(labelTF); textFormatsLabel.embedFonts=true; textFormatsLabel.setTextFormat(labelTF); //TEXTFIELDS txf1.embedFonts=true; txf1.setTextFormat(ml.getTextFormatById("lucidaGBold10")); txf2.embedFonts=true; txf2.setTextFormat(ml.getTextFormatById("lucidaGBold12")); //use shortcuts for other textformats. utils.text.textFormat(txf3,ml.getTextFormatById("lucidaGRegular10")); utils.text.textFormat(txf4,ml.getTextFormatById("lucidaGRegular12")); //another shortcut type. utils.setters.textFormat(ml.getTextFormatById("lucidaGRegular10"),txf5,txf6,txf7); //STYLESHEETS ss1.embedFonts=true; ss1.styleSheet=ml.getStyleSheetById("example"); ss1.htmlText="<span class=\"body\">"+ss1.text+"</span>"; ss2.embedFonts=true; ss2.styleSheet=ml.getStyleSheetById("example2"); ss2.htmlText="<span class=\"body\">"+ss2.text+"<span class=\"bold\">. and this is bold.</span></span>"; //stylsheet shortcuts utils.text.styleSheet(ss3,ml.getStyleSheetById("example")); ss3.htmlText="<span class=\"body\">"+ss3.text+"</span>"; utils.text.styleSheet(ss4,ml.getStyleSheetById("example")); ss4.htmlText="<span class=\"body\">"+ss4.text+"</span>"; //another shortcut type utils.setters.stylesheet(ml.getStyleSheetById("example"),ss5,ss6,ss7); ss5.htmlText="<span class=\"body\">"+ss5.text+"</span>"; ss6.htmlText="<span class=\"body\">"+ss6.text+"</span>"; ss7.htmlText="<span class=\"body\">"+ss7.text+"</span>"; } } }
This example uses some other great shortcuts for preparing text fields for stylesheets, or text formats:
- utils.text.textFormat - utils.text.textWithTextFormat - utils.text.textWithTextFormatFromLocaleString - utils.text.textWithFormattingFromXML - utils.text.styleSheet - utils.setters.textFormat - utils.setters.styleSheet
I’m not going to explain all of those, I’d recommend reading the source for those methods. As well as reading the source for the two model methods (getTextFormatById, and getStyleSheetById).
I put this example in the guttershark repository for all. Pull down the latest version, and checkout “examples/other/font_libs/ex1″. You can get the latest version in git.
enjoy.
3 commentsThe Guttershark DocumentController
Continuing on from assets and the model, Let’s take a look at the DocumentController.
The DocumentController does quite a few things. I’ll show you three of them: loading model xml files, handling flashvars, and kicking off your app after everything is done.
The most important thing when writing flash websites, is that you pass in paths from flashvars. For example, passing in asset locations, xml locations, and of course, what the model xml file is.
Flashvars are great - they allow you to put path configuration in javascript, which gives you a hook right before the swf is embedded. That can be used for making decisions based off of the url it’s being embedded at. That can also lead to a problem - how do I handle flashvars when I’m publishing the FLA in the Flash IDE? Should I just publish, but then close the window and open the browser? Or should I put some hard coded information in the main class, for that situation?
This problem is fixed with the guttershark DocumentController. The method DocumentController.flashvarsForStandalone is called when you run the swf in the Flash IDE, or as a standalone swf.
For the first example, let’s assume you are setting it up to only run from HTML.
Here’s the HTML snippet:
<!-- ... -->
<div id="flash"></div>
<script>
var vars={
model:"assets/xml/model.xml"
}
var params = {scale:'noScale'}
var attributes = {id:'flaswf',name:'flaswf'}
swfobject.embedSWF("main.swf","flash","600","400","9.0.0",null,vars,params,attributes)
</script>
<!-- ... --> Simple right? The guttershark document controller looks for a flashvar called “model”, if found, the doc controller will load that xml, and give it to the Model.
Here’s the document controller:
package { import net.guttershark.control.DocumentController; public class TestPreloader extends DocumentController { override protected function setupComplete():void { trace( ml.xml ); } } }
The setupComplete method is a hook for you. It’s essentially your “constructor” for the application, it runs after all startup logic is done. And in our case, it’s only loading the model xml.
If you try this out, you’ll notice that when you’re in the Flash IDE, the trace will give you “null” - there’s that problem I mentioned. We need some way of giving the document controller “alternative” flashvars when running in standalone mode. Here’s how to integrate that:
package { import net.guttershark.control.DocumentController; public class TestPreloader extends DocumentController { override protected function flashvarsForStandalone():Object { return { model:"assets/xml/model.xml" } } override protected function setupComplete():void { trace( ml.xml ); } } }
The flashvarsForStandalone method is called as one of the first method calls in the doc controller startup process - and it only get’s called if the swf is indeed running as the standalone player. If the swf is embedded on an HTML page, this method is ignored.
So, with this in mind, let’s integrate our “path” logic from the previous post.
Here’s the updated HTML snippet:
<div id="flash"></div>
<script>
var vars={
model:"assets/xml/model.xml",
assets:"assets/",
bitmaps:"assets/bitmaps/",
}
var params = {scale:'noScale'}
var attributes = {id:'flaswf',name:'flaswf'}
swfobject.embedSWF("main.swf","flash","600","400","9.0.0",null,vars,params,attributes)
</script> Here’s the updated code:
package { import net.guttershark.control.DocumentController; public class TestPreloader extends DocumentController { override protected function flashvarsForStandalone():Object { return { model:"assets/xml/model.xml", assets:"assets/", bitmaps:"assets/bitmaps/" } } override protected function initPaths():void { //"ml" is already a protected property on doc controller. ml.addPath("bitmaps",flashvars.bitmaps); } override protected function setupComplete():void { trace( ml.xml ); } } }
The document controller has a property called “flashvars”. If the swf is in standalone, it will be set to the object you return from flashvarsForStandalone, or if the swf is embedded in HTML, it will be set to the flashvars that are embedded on the swf.
There’s one new method to point out - initPaths. This method is a hook that the doc controller gives you. It’s called early in the setup process (but after flashvars are ready). Because the initPaths is called after the flashvar logic, all you need to do is add paths to the Model from flashvars. So, in our case, the only path we need is “bitmaps”.
One other key piece of information - the flashvars from HTML and flashvarsForStandalone don’t need to define the exact same values. This is very important. What if when you’re developing in the FlashIDE, you need to change the paths to be relative, but when you deploy the site, the paths need to be absolute? This is exactly why you have an “alternative” way to define flashvars for standalone.
Now, let’s go ahead and integrate everything from the last two posts.
Here’s the model xml:
<?xml version="1.0" encoding="utf-8"?> <model> <assets> <group id="pictures"> <asset libraryName="myPicture" src="myPicture.jpg" path="bitmaps" preload="true" /> <asset libraryName="myOtherPicture" src="myOtherPicture.jpg" path="bitmaps" preload="true" /> </group> </assets> </model>
Make note of the new “preload” attribute above.
Here’s the HTML again:
<!-- ... -->
<div id="flash"></div>
<script>
var vars={
model:"assets/xml/model.xml",
assets:"assets/",
bitmaps:"assets/bitmaps/"
}
var params = {scale:'noScale'}
var attributes = {id:'flaswf',name:'flaswf'}
swfobject.embedSWF("main.swf","flash","600","400","9.0.0",null,vars,params,attributes)
</script>
<!-- ... -->And finally, here’s the entire document controller code:
package { /* .. */ public class TestPreloader extends DocumentController { public var progress:MovieClip; override protected function flashvarsForStandalone():Object { return { model:"assets/xml/model.xml", assets:"assets/" bitmaps:"assets/bitmaps/" } } override protected function initPaths():void { ml.addPath("bitmaps",flashvars.bitmaps); } override protected function setupComplete():void { trace( ml.xml ); preloadAssets(); } private function preloadAssets():void { pc = new PreloadController(300); pc.addEventListener(Event.COMPLETE,onPreloadComplete); pc.addEventListener(PreloadProgessEvent.PROGRESS,onProgress); /* new method - getAssetsForPreload() */ pc.addItems(ml.getAssetsForPreload()); pc.start(); } private function onPreloadComplete(e:Event):void { trace("preloading complete"); addChild( am.getBitmap("myPicture") ); } private function onProgress(ppe:PreloadProgessEvent):void { trace(ppe); progress.width=ppe.pixels; } } }
This is starting to shape up nicely.
There was one method added here, getAssetsForPreload(). This method returns an array of all Asset definitions from the model that contain the “preload=true” attribute.
Hopefully you can see how helpful this all is, and how guttershark makes it simple!
It’s also very important to note that there isn’t anything “hard-coded” anymore, with the exception of the initFlashvarsForStandalone - which is only for flash IDE development anyway.
And, sure, we still have the string “myPicture” hard coded. But, what you start to see is that you change everything around the asset “myPicture”. myPicture is now just an ID to some asset, and we can easily change everything else about the application, and as long as the paths from flashvars are pointing to the right location - it will all work.
Conclusion
It’s great! We went from having quite a bit of code with hard coded assets in it. To less code, that does everything for me.
You can also start to see how nothing in Guttershark is really bound to anything else. If you wanted to write a new document controller to replace guttersharks built in doc controller - you certainly could do that. All while being able to use the rest of guttershark - like the model, asset manager, preloaders, etc.
Where do you go from here? I would strongly suggest reading the source code for the DocumentController, Model, PreloadController, and AssetManager, CoreClip, and CoreSprite. Those are the most important pieces of Guttershark. Everything else is gravy on top.
11 commentsAssets and the Model Class
Continuing on from Preloading and Asset Management. Let’s look at how the model can help with asset definitions, and removing hard coded assets.
The Model class in Guttershark is one of a couple “brains” in the library. It can do lots of stuff for you, but today, we’ll just focus on assets and paths. I’m going to continue using the example from Preloading and Asset Management - just adding and removing some code.
Before we get too far into this, make note of the PreloadController.addItems method. Notice that it takes an array of items (Asset instances).
Now take a look at these methods on the Model class. (getAssetByLibraryName, getAssetsByLibraryNames, getAssetGroup).
The Model class works with an XML object - which must be structured a certain way, then the model can do all this work for you.
To start getting this all setup, let’s first look at the most basic XML file, which defines some assets. This xml will show you how to use two of the model methods.
<?xml version="1.0" encoding="utf-8"?> <model> <assets> <!-- for the model method, getAssetsByLibraryNames --> <asset libraryName="myPicture" src="assets/myPicture.jpg" /> <asset libraryName="myOtherPicture" src="assets/myOtherPicture.jpg" /> <!-- for the model method, getAssetGroup --> <group id="pictures"> <asset libraryName="myPicture" src="assets/myPicture.jpg" /> <asset libraryName="myOtherPicture" src="assets/myOtherPicture.jpg" /> </group> </assets> </model>
Ok, so with that in mind - here’s our first example of using this:
package { import flash.events.Event; import flash.net.URLRequest; import flash.display.MovieClip; import flash.display.Sprite; import net.guttershark.control.PreloadController; import net.guttershark.support.preloading.Asset; import net.guttershark.support.preloading.events.PreloadProgressEvent; import net.guttershark.util.XMLLoader; import net.guttershark.model.Model; public class TestPreloader extends Sprite { private var ml:Model; private var am:AssetManager; private var pc:PreloadController; private var assets:Array; private var xmlLoader:XMLLoader; public var progress:MovieClip; public function TestPreloader() { am = AssetManager.gi(); loadModelXML(); } private function loadModelXML():void { xmlLoader=new XMLLoader(); xmlLoader.contentLoader.addEventListener(Event.COMPLETE,onModelLoaded); xmlLoader.load(new URLRequest("model.xml")); } private function onModelLoaded(e:*):void { ml=Model.gi(); ml.xml=new XML(xmlLoader.data); preloadAssets(); } private function preloadAssets():void { pc = new PreloadController(300); //remember.. 300 = the pixelsToFill. pc.addEventListener(Event.COMPLETE,onPreloadComplete); //when preloading all items complete. pc.addEventListener(PreloadProgessEvent.PROGRESS,onProgress); //progress for all items as a whole. /* first way to get assets, using getAssetsByLibraryNames */ assets = ml.getAssetsByLibraryNames("myPicture","myOtherPicture"); /* second way, using groups */ assets = ml.getAssetGroup("pictures"); pc.addItems(assets); pc.start(); } private function onPreloadComplete(e:Event):void { trace("preloading complete"); addChild( am.getBitmap("myPicture") ); } private function onProgress(ppe:PreloadProgessEvent):void { trace(ppe); progress.width=ppe.pixels; } } }
It’s simple right? But wait, there’s more!
The model xml has some room for improvement - getting rid of the asset paths. For example, in the model xml, each asset defines the “src” attribute to equal something like: “assets/XXX”. It would be nice to not have to define that all the time. So how can we change it?
The Model and Paths
The Model class has two methods for working with paths. addPath and getPath.
Before we integrate these methods into the code, here’s a sand-boxed code example of how the path methods work:
var ml=Model.gi(); //the preferred way: ml.addPath("root","http://example.com/"); ml.addPath("assets", ml.getPath("root")+"assets/") ); ml.adddPath("xml", ml.getPath("assets")+"xml/") ); trace( ml.getPath("xml") ); // http://example.com/assets/xml/ //but, you can also define just the pieces of a path, and use ...rest parameters with the getPath method. ml.addPath("root","http://example.com/"); ml.addPath("assets","assets/"); ml.addPath("xml","xml/"); trace( ml.getPath("root","assets","xml") ); //http://example.com/asset/xml/
By convention, you should always add trailing slashes at the end of a path. This makes sure you never have to write parse code to remove double slashes (//), or testing to see if the index of a slash is the last character, etc.
That’s pretty easy right? We can integrate that into the model xml, and code.
Here’s the new model xml, note that I’m only going to use one style for grabbing assets - the group ids.
<?xml version="1.0" encoding="utf-8"?> <model> <assets> <group id="pictures"> <asset libraryName="myPicture" src="myPicture.jpg" path="bitmaps" /> <asset libraryName="myOtherPicture" src="myOtherPicture.jpg" path="bitmaps" /> </group> </assets> </model>
Notice the new attribute, “path”. All of the methods on the Model class that help with getting you assets, will look for this path attribute, and then look in the model for path definitions that match that, and prepend it onto the src attribute.
Here’s our updated example:
package { import flash.events.Event; import flash.net.URLRequest; import flash.display.MovieClip; import flash.display.Sprite; import net.guttershark.control.PreloadController; import net.guttershark.support.preloading.Asset; import net.guttershark.support.preloading.events.PreloadProgressEvent; import net.guttershark.util.XMLLoader; import net.guttershark.model.Model; public class TestPreloader extends Sprite { private var ml:Model; private var am:AssetManager; private var pc:PreloadController; private var assets:Array; private var xmlLoader:XMLLoader; public var progress:MovieClip; public function TestPreloader() { am = AssetManager.gi(); loadModelXML(); } private function loadModelXML():void { xmlLoader=new XMLLoader(); xmlLoader.contentLoader.addEventListener(Event.COMPLETE,onModelLoaded); xmlLoader.load(new URLRequest("model.xml")); } private function onModelLoaded(e:*):void { ml=Model.gi(); ml.xml=new XML(xmlLoader.data); /** define paths. **/ ml.addPath("bitmaps","assets/bitmaps/"); preloadAssets(); } private function preloadAssets():void { pc = new PreloadController(300); //remember.. 300 = the pixelsToFill. pc.addEventListener(Event.COMPLETE,onPreloadComplete); //when preloading all items complete. pc.addEventListener(PreloadProgessEvent.PROGRESS,onProgress); //progress for all items as a whole. assets = ml.getAssetGroup("pictures"); pc.addItems(assets); pc.start(); } private function onPreloadComplete(e:Event):void { trace("preloading complete"); addChild( am.getBitmap("myPicture") ); } private function onProgress(ppe:PreloadProgessEvent):void { trace(ppe); progress.width=ppe.pixels; } } }
There’s only one minor change to the code, I’ve added one line that defines the “bitmaps” path with the model.
Conclusion
That’s all there is to assets and paths with the model class. Hopefully you begin to see how much work Guttershark can really do for you. Guttershark is all about eliminating code. I hate writing code, don’t you? I actually love writing code, but the code that I hate writing is the same code i’ve already written on another project.
You may have noticed, there’s even more room for improvement. There are a few things here that should definitely not have to be written again. And there’s still a few things that are “hard coded”.
What I’ll show you next is the DocumentController. It takes care of loading your model xml, and gives you hooks to initialize paths with the model.
With Guttershark and the DocumentController, you’ll see that this example we’ve been working on will be significantly reduced - imagine that, event less code?
9 commentsPreloading and Asset Management with Guttershark
I thought I’d share a quick article on how and why preloading, and asset management is so simple with guttershark.
The preload controller is designed to be the one access point for loading any type of files. As some of you might be snooping through the guttershark library - you’ll see that there are multiple classes that help do the work for the PreloadController, which are called “Workers”. Don’t be fooled by all the support classes - you don’t ever need to use them.
Basic example
Let’s look at an example, this only sets up the preloader to load in some assets, we’ll look at getting them out in a bit.:
package { import flash.events.Event; import net.guttershark.control.PreloadController; import net.guttershark.support.preloading.Asset; import net.guttershark.support.preloading.events.PreloadProgressEvent; public class TestPreloader extends Sprite { private var pc:PreloadController; private var assets:Array; public function TestPreloader() { pc = new PreloadController(); pc.addEventListener(Event.COMPLETE,onPreloadComplete); //when preloading all items complete. pc.addEventListener(PreloadProgessEvent.PROGRESS,onProgress); //progress for all items as a whole. //pc.addEventListener(AssetProgressEvent,onAssetProgress); //fired for every asset's individual progress. //**there are move events available. see docs for those.** //now let's add some assets assets=[]; assets.push( new Asset("assets/myPicture.jpg") ); assets.push( new Asset("assets/myOtherPicture.jpg") ); //add the assets pc.addItems(assets); //now let's kick off the preloader pc.start(); } private function onPreloadComplete(e:Event):void { trace("preloading complete"); } private function onProgress(ppe:PreloadProgressEvent):void { trace(ppe); } } }
What you’re looking at is the most basic form of preloading with guttershark. In the above example, we’re loading jpg’s. Internally to the Asset class, it uses a “BitmapWorker” to load in the asset. This is something you don’t have to specify, it will figure out what type of content your asset is based off of the file extension.
That raises a question - what about loading files that don’t have an extension? With asset instances, you can specify a “forced file type”, which informs the Asset of which worker to use. For example:
var rssAsset:Asset = new Asset("http://example.com/rss?feed=atom",null,"xml");
Ignore the second “null” parameter for now, I’ll get to that. The third parameter, is how you set a “forcedFileType”. So this asset will be loaded with the XMLWorker.
Showing progress
Guttershark has some cool conventions on multiple classes, which will automatically give you a pixel value based on some other type of value, such as progress, time, etc.
The PreloadProgressEvent has a property called “pixels”. This value is based off of what you tell the preloader to “fill”. For example, let’s say we wanted our preloader to fill 300 pixels. Keep in mind, what you do with this “pixel” value is up to you. It could be mapped to height, width, etc. Here’s the example (this is in addition to the above example):
package { /*..*/ public class TestPreloader extends Sprite { /*..*/ public var progress:MovieClip; public function TestPreloader() { pc = new PreloadController(300); //(this is key, it specifies the "pixelsToFill") /*..*/ } private function onPreloadComplete(e:Event):void { trace("preloading complete"); } private function onProgress(ppe:PreloadProgessEvent):void { /*..*/ progress.width=ppe.pixels; } } }
In the above example, the first argument to the preload controller constructor is the “pixelsToFill” property. Which is how the pixel value is delivered to you in the event handler.
There are a few other classes that offer this same type of behavior. The FLV class is another example - it will give you a “pixelsPlayed” property. Which you map to a progress bar. Read more of the guttershark docs for that.
Ok, that covers the majority of working with the preload controller. There are a few other methods, and properties that may be of use. You can check those out in the docs.
On to using the assets.
Accessing Assets with the AssetManager
Every preload controller you use stores all of the assets loaded in the AssetManager. The asset manager is a singleton - this creates a globally accessible asset library, so it doesn’t matter in which class, or in what scope assets are loaded - they all get into the asset manager.
The preload controller also (by default), loads all assets into the same application domain. You’ll see what that does in a bit.
Remember that “null” parameter for the Asset class from a few paragraphs ago? That parameter is called the “libraryName”. When you specify the library name for an asset, it registers that asset in the asset manager by that id.
So, to slightly alter the example so far, to attach a bitmap onto the stage. It would look like this (here’s the complete example):
package { import flash.events.Event; import net.guttershark.control.PreloadController; import net.guttershark.support.preloading.Asset; import net.guttershark.support.preloading.events.PreloadProgressEvent; public class TestPreloader extends Sprite { private var am:AssetManager; private var pc:PreloadController; private var assets:Array; public var progress:MovieClip; public function TestPreloader() { am = AssetManager.gi(); pc = new PreloadController(300); pc.addEventListener(Event.COMPLETE,onPreloadComplete); pc.addEventListener(PreloadProgessEvent.PROGRESS,onProgress); //now let's add some assets assets=[]; assets.push( new Asset("assets/myPicture.jpg", "myPicture" ) ); assets.push( new Asset("assets/myOtherPicture.jpg", "myOtherPicture") ); //add the assets pc.addItems(assets); //now let's kick off the preloader pc.start(); } private function onPreloadComplete(e:Event):void { trace("preloading complete"); addChild( am.getBitmap("myPicture") ); addChild( am.getBitmap("myOtherPicture") ); } private function onProgress(ppe:PreloadProgessEvent):void { trace(ppe); progress.width=ppe.width; } } }
Again, the important piece of code in this example, is the addition of the second parameter to the Asset instance. Each one has a library name, which will make that asset available in the asset manager.
For another example, let’s look at how you can load swfs, and grab assets out of the swf’s library, I’ve altered the asset that is loaded, and what happens when the preload is complete.:
package { /**/ public class TestPreloader extends Sprite { private var am:AssetManager; private var pc:PreloadController; private var assets:Array; public var progress:MovieClip; public function TestPreloader() { am = AssetManager.gi(); pc = new PreloadController(); /*..*/ //now let's add some assets assets=[]; assets.push( new Asset("assets/myAssetsSWF.swf", "assets" ) ); /*..*/ } private function onPreloadComplete(e:Event):void { trace("preloading complete"); addChild( am.getMovieClipFromSWFLibrary("assets", "myMovieClip") ); } /* .. */ } }
You should hopefully start to see some of the elegance with the preload controller, and asset manager.
How do application domains play into this picture? In the above example, I’m calling the method “getMovieClipFromSWFLibrary”. This method does like it sounds, it grabs an asset out of that swf’s application domain. There are other methods to get other assets as well, like getBitmapFromSWFLibrary, getSpriteFromSWFLibrary, etc.
That’s well and good, but we can take this further - because the preload controller loads everything into one application domain, technically you don’t have to tell the asset manager which swf that asset is in. So, we could alter this example like this:
package { /**/ public class TestPreloader extends Sprite { private var am:AssetManager; private var pc:PreloadController; private var assets:Array; public var progress:MovieClip; public function TestPreloader() { am = AssetManager.gi(); pc = new PreloadController(); /*..*/ //now let's add some assets assets=[]; assets.push( new Asset("assets/myAssetsSWF.swf", "assets" ) ); /*..*/ } private function onPreloadComplete(e:Event):void { trace("preloading complete"); addChild( am.getMovieClip("myMovieClip") ); } /* .. */ } }
Now you can see, that it doesn’t matter which swf the clip “myMovieClip” is defined in, because the swf is merged into one application domain.
This does raise one problem. Multiple library items, with the same export name, in different swfs. For example, if I have two swfs (swf1, swf2), and they both have the clip “GlossButton”. The last one to be loaded in will be discarded. If swf2 is loaded after swf1, the GlossButton in swf2 will be discarded. That’s the only pitfall to watch out for when using one application domain.
Conclusion
The PreloadController can load anything for you. It can load bitmap, swf, flv, xml, audio, and stylesheet (css) files.
Let me quickly note - manually creating Asset instances is a no-no! I never actually create Asset instances in code, the examples were to illustrate how the preload controller works.
So, in the next post, I’ll show you how to get rid of that annoying code that requires you to manually create Asset instances - we’ll do that with XML and the Model class.
And as always, read the docs.
2 commentsXML Namespaces in AS3: XMLNamespaceProxy
Quick note about a neato class I just added to guttershark. I’ve been working with SOAP in AS3 lately. Man is it a pain in the butt. So I’m doing my best to make sure I don’t have to write this crap again.
One other quick note before I show you the namespace proxy. I’ve integrated a SoapService in with the ServiceManager. It’s fairly easy to setup now. You can read all about that in the docs.
On to the XMLNamespaceProxy. With soap, when you get a result back - there are a couple steps you need to go through. Usually looks something like this:
var result:XML=new XML(mySoapResultXML); var ns:Namespace=result.namespace(); var body:XML=result.ns::Body;
This sets you up with whatever is in the body of the soap result. Which is pretty simple. But now I have to snoop through the content of the body - which in my case (the service I’m using) has custom namespaces in the result. Here’s my initial code looked like:
var targetNodes:XML=body.ns::rawValues.ns::bud09Out; trace(targetNodes.ns::totalIncome); //give me a value im looking for
The service I’m hitting has namespaces in the responses, so in order to find the actual values needed I have to prefix everything with the qualifying namespace. Arg!
Here’s entire code snippet with xml namespace proxy integrated:
var result:XML=new XML(mySoapResultXML); var ns:Namespace=result.namespace(); var body:XML=result.ns::Body; var targetNodes:XML=body.ns::rawValues.ns::bud09Out; //the good stuff: var xnp:XMLNamespaceProxy=new XMLNamespaceProxy(targetNodes,ns); //now I can use the xml namespace proxy just like normal: trace(xnp.totalIncome); //or... trace(xnp.years.year[0].@category);
After you create an XMLNamespaceProxy object, every call get’s prefixed with the qualifying namespace and forwarded to the actual XML object. No more namespace prefix’s. Yay!
As you’re reading through the XML. The proxy will return instances of XMLNamespaceProxy’s (if the target is XML), or it will return a normal XMLList which you can iterate over.
And as usual, check the docs.
Enjoy!
4 commentsChain-able Decorators in AS3 With Proxy
[UPDATE: Some of the source files in this example won't be available. Please see the latest files in the guttershark repo, in gs.display.decorators]
I was briefly examining the decorator pattern in the “Advanced ActionScript 3 with Design Patterns,” which is great. But the mention of “chaining decorators” wasn’t fully brought to fruition.
I spent some time thinking about how exactly one would implement them with display objects. At first examination, the problem is that with a decorator, it needs to implement the same interface as the decorated object - which presents a problem. I don’t want to write the entire movie clip, or sprite methods. Bleh.
The solution is to create a decorator that extends proxy. If you call a method, or read a property on the decorator that’s unknown, forward the call to decorated object. Another key, is to make sure the decorator requires a dynamic object as the object to decorate.
Maybe that description was a little bit hard to understand. These examples should help explain:
First, make note of these two includes:
Then take a gander through these three decorators:
Optionally, with the “highlightable” sprite. It supports something called proxy through. Which is just a creative way for me to specify a different object to send unknown method calls or properties to. That’s what the second include is for.
The only awkward thing is that you can’t use these decorators with the display list. So you’d still have to add the base object to the display list, but then you can chain the decorators and use the outer-most decorator for methods/properties.
The examples zip file has updates for this new functionality.
Make note, that because the decorators use includes, you’ll need to make sure you’ve got the as and as_includes folder from the guttershark source. The reason the includes are outside of the classpath is because asdoc interprets the .as files as a class, which causes errors. And the reason for the includes in general is so I don’t have to repeat myself.
Enjoy.
4 commentsGuttershark Updates
Hey all, just wanted to mention that you can follow updates to guttershark by subscribing to the rss feed that gitweb creates. There has been activity over the last week, with great updates from Oliver Turner to the layout manager. A few updates from me, and more to come from both of us.
The layout manager now has the ability to align things to the inner or outer edges of objects, and one more method was added, alignColumn, which aligns objecs in a vertical stack, but has a few good options.
I’ve also, finally added a decent scroll bar class (net.guttershark.display.ScrollBar), which doesn’t suck, doesn’t break, and doesn’t require all objects to be on zero-zero points.
Besides the scrollbar, I’m working on BaseFLVControls, which will be a really quick way to get flv controls into a player. But because it’s extendable, you can override methods that provide hooks into the various events that are causing buttons to show and hide, etc. Look for that to be complete in a few days.
Also, I don’t know if anyone looks at the decorator classes, they seem simple, but they’re really useful. I just added a new one called “HighlightableSprite.” Which wraps up over and out states, but in a simple way. So, when used, you can get over and out states in one line of code.
The examples zip has been updated too. Which includes updates to the layout manager fla (thanks to Oliver Turner), examples of the new scrollbar and baseflvcontrols, and the highlightable sprite.
7 commentsNew Guttershark Logo
Thanks to Michael Trueman for the new logo! Check out the Guttershark page to see it - it’s awesome!
No commentsGuttershark Binary Updates
After doing a little more research about COMPC, I found I wasn’t optimizing or compressing the output as much as it could be. From now on, there will be two SWCS - one with debugging and no optimization, and another without debugging, and with optimization. I also figured out how to optimize the Guttershark SWF even more, using the “optimization” command line tool in the flex SDK - it cuts off about 20K. See the Guttershark page for more info.
No commentsJSFL Proxy Class - Alleviate MMExecute Pain
Those of you who have written JSFL know the pain of MMExecute - with having to wrap quotes, inside of double quotes, which could have more quotes or slashes. I’ve been writing a number of WindowSWF tools - which use a lot of JSFL. This JSFL Proxy class will help alleviate all the work.
The JSFLProxy class alleviates calling JSFL scripts, or methods inside of a JSFL script. It also adds support for escaping parameters, unescaping the responses, and allowing you to specify a return type from JSFL - which, amazingly (or not so) enough, allows me to cast the JSFL response before it’s returned to you.
Here’s a demonstration:
var jp:JSFLProxy=JSFLProxy.gi(); var functionLib:String="file://XXX/myFunctionLibrary.jsfl"; var dir:String = jp.runScript(functionLib,{method:"getSomeDirectory",params:[theDirectory],responseWasEscaped:true}); var dirContents:Array = jp.runScript(functionLib,{method:"getDirContents",escapeParams:true,responseFormat:"array",parameters:[dir],responseWasEscaped:true});
There are a few other methods available, and support for a few other response formats. JSFLProxy is available in Guttershark. Here’s a direct link to the JSFLProxy documentation.
No comments