Thursday, December 9, 2010

AS3 Command Pattern for Rapid Asset Development

You know those little modular swf projects where you just program in some basic functionality in lieu of using the timeline? Well, I have had a few of those lately, so I made a little module manager.

It's based upon a useful tutorial I ran across and tweaked a bit. I created a google code repository to host the result, called as3minimalcommandpattern.

See this demo which uses only this library. All the code from the demo is pasted below ( the demo can be found with source in the repository).

The demo consists of the document class and as many 'scenes' as are required for the project. A scene in this sense is simply a way to encapsulate an intro-outro sequence. It's good for doing the monotonous work for you so you can focus on integrating advanced features.

Document Class:

package
{
import com.bluediesel.utils.managers.scenemanager.SceneManager;
import flash.display.MovieClip;
import scenes.*;

public class Main extends MovieClip
{
public function Main()
{
var sceneManager:SceneManager = new SceneManager();
sceneManager.setScene(new IntroScene(this));
}
}
}

IntroScene (Setup the UI):

package scenes
{
import com.bluediesel.utils.managers.scenemanager.commands.*;
import com.bluediesel.utils.managers.scenemanager.commands.greensock.*;
import com.bluediesel.utils.managers.scenemanager.scenes.*;
import com.greensock.plugins.*;
import fl.transitions.easing.Strong;

public class IntroScene extends AbstractScene implements IScene
{
public function IntroScene(c:Object) {

TweenPlugin.activate([AutoAlphaPlugin]);
super(c);
}

override public function createIntroCommand():Command
{
return new SerialCommand( 0.1,
new TweenMaxTo ( c.backgroundMC, 0.1, { blurFilter:{blurX:20, blurY:20} } ),
new TweenMaxTo ( c.backgroundMC, 0.4, { autoAlpha:1, blurFilter:{blurX:0, blurY:0} } ),
new ParallelCommand( 0,
new TweenMaxTo( [c.uiTopBanner, c.uiBottomBanner, c.buttonPrevious, c.buttonNext], 0.2, { autoAlpha:1 } ),
new TweenMaxFrom( [c.uiTopBanner, c.uiBottomBanner, c.buttonPrevious, c.buttonNext], 0.3, { y:500, easing:Strong.easeOut } )
),
new TweenMaxTo ( [c.appTitle, c.clickForSoundText], 0.2, { autoAlpha:1 } )
);
}

override public function onSceneSet():void {
sceneManager.setScene( new Scene01(c) );
}
}
}

Scene01 (Intro-Outro Sheep):

package scenes
{
import com.bluediesel.utils.managers.scenemanager.commands.*;
import com.bluediesel.utils.managers.scenemanager.commands.events.*;
import com.bluediesel.utils.managers.scenemanager.commands.greensock.*;
import com.bluediesel.utils.managers.scenemanager.commands.utils.SetProperties;
import com.bluediesel.utils.managers.scenemanager.scenes.*;

import fl.transitions.easing.Strong;

import flash.events.MouseEvent;
import flash.media.*;

public class Scene01 extends AbstractScene implements IScene
{
public function Scene01(c:Object)
{
super(c);
}

override public function createIntroCommand():Command
{
return new SerialCommand(0,
new TweenMaxTo ( c.sheepTitle, 0.2, { autoAlpha:1 } ),
new TweenMaxTo ( c.sheepMC, 0.2, { autoAlpha:1 } ),
new TweenMaxTo ( c.bodyCopyBG, 0.2, { height:231, width:470, autoAlpha:1, easing:Strong.easeOut } ),
new TweenMaxTo ( c.sheepBodyCopy, 0.2, { autoAlpha:1 } ),
new SetProperties ( [c.buttonPrevious, c.buttonNext, c.sheepMC, c.horseMC, c.pigMC], {buttonMode:true, useHandCursor:true} ),
new AddEventListener( c.sheepMC, MouseEvent.MOUSE_DOWN, playNoise ),
new AddEventListener( c.buttonPrevious, MouseEvent.MOUSE_DOWN, handlePrevious ),
new AddEventListener( c.buttonNext, MouseEvent.MOUSE_DOWN, handleNext )
);
}

override public function createOutroCommand():Command
{
return new SerialCommand(0,
new RemoveEventListener( c.sheepMC, MouseEvent.MOUSE_DOWN, playNoise ),
new RemoveEventListener( c.buttonPrevious, MouseEvent.MOUSE_DOWN, handlePrevious ),
new RemoveEventListener( c.buttonNext, MouseEvent.MOUSE_DOWN, handleNext ),
new TweenMaxTo ( c.sheepBodyCopy, 0.1, { autoAlpha:0 } ),
new TweenMaxTo ( c.bodyCopyBG, 0.1, { height:0, width:0, autoAlpha:0, easing:Strong.easeOut } ),
new TweenMaxTo ( c.sheepMC, 0.1, { autoAlpha:0 } ),
new TweenMaxTo ( c.sheepTitle, 0.1, { autoAlpha:0 } )
);
}

override public function onSceneSet():void
{
//TODO: implement function
}

/* CUSTOM METHODS */
private function handlePrevious(e:MouseEvent):void
{
trace ("Already At Beginning");
//sceneManager.setScene( new IntroScene( c ) );
}

private function handleNext(e:MouseEvent):void
{
sceneManager.setScene( new Scene02 ( c ) );
}

private function playNoise(e:MouseEvent):void{
var soundChannel:SoundChannel = new SoundChannel();
soundChannel = new SheepNoise().play();
}
/* END CUSTOM METHODS */
}
}

Scene02 (Intro-Outro Horse):

package scenes
{
import com.bluediesel.utils.managers.scenemanager.commands.*;
import com.bluediesel.utils.managers.scenemanager.commands.events.*;
import com.bluediesel.utils.managers.scenemanager.commands.greensock.*;
import com.bluediesel.utils.managers.scenemanager.scenes.*;

import fl.transitions.easing.Strong;
import flash.events.MouseEvent;
import flash.media.*;

public class Scene02 extends AbstractScene implements IScene
{
public function Scene02(c:Object)
{
super(c);
}

override public function createIntroCommand():Command
{
return new SerialCommand(0,
new TweenMaxTo( c.horseTitle, 0.2, { autoAlpha:1 } ),
new TweenMaxTo( c.horseMC, 0.2, { autoAlpha:1 } ),
new TweenMaxTo ( c.bodyCopyBG, 0.2, { height:231, width:470, autoAlpha:1, easing:Strong.easeOut } ),
new TweenMaxTo( c.horseBodyCopy, 0.2, { autoAlpha:1 } ),
new AddEventListener( c.horseMC, MouseEvent.MOUSE_DOWN, playNoise ),
new AddEventListener( c.buttonPrevious, MouseEvent.MOUSE_DOWN, handlePrevious ),
new AddEventListener( c.buttonNext, MouseEvent.MOUSE_DOWN, handleNext )
);
}

override public function createOutroCommand():Command
{
return new SerialCommand(0,
new RemoveEventListener( c.horseMC, MouseEvent.MOUSE_DOWN, playNoise ),
new RemoveEventListener( c.buttonPrevious, MouseEvent.MOUSE_DOWN, handlePrevious ),
new RemoveEventListener( c.buttonNext, MouseEvent.MOUSE_DOWN, handleNext ),
new TweenMaxTo ( c.horseBodyCopy, 0.1, { autoAlpha:0 } ),
new TweenMaxTo ( c.bodyCopyBG, 0.1, { height:0, width:0, autoAlpha:0, easing:Strong.easeOut } ),
new TweenMaxTo ( c.horseMC, 0.1, { autoAlpha:0 } ),
new TweenMaxTo ( c.horseTitle, 0.1, { autoAlpha:0 } )
);
}

override public function onSceneSet():void
{
}

/* CUSTOM METHODS */
private function handlePrevious(e:MouseEvent):void
{
sceneManager.setScene( new Scene01 ( c ) );
}

private function handleNext(e:MouseEvent):void
{
sceneManager.setScene( new Scene03 ( c ) );
}

private function playNoise(e:MouseEvent):void{
var soundChannel:SoundChannel = new SoundChannel();
soundChannel = new HorseNoise().play();
}
/* END CUSTOM METHODS */
}
}

Scene03 (Intro-Outro Pig):

package scenes
{
import com.bluediesel.utils.managers.scenemanager.commands.*;
import com.bluediesel.utils.managers.scenemanager.commands.events.*;
import com.bluediesel.utils.managers.scenemanager.commands.greensock.*;
import com.bluediesel.utils.managers.scenemanager.scenes.*;

import fl.transitions.easing.Strong;
import flash.events.MouseEvent;
import flash.media.*;


public class Scene03 extends AbstractScene implements IScene
{
public function Scene03(c:Object)
{
super(c);
}

override public function createIntroCommand():Command
{
return new SerialCommand(0,
new TweenMaxTo( c.pigTitle, 0.2, { autoAlpha:1 } ),
new TweenMaxTo( c.pigMC, 0.2, { autoAlpha:1 } ),
new TweenMaxTo ( c.bodyCopyBG, 0.2, { height:231, width:470, autoAlpha:1, easing:Strong.easeOut } ),
new TweenMaxTo( c.pigBodyCopy, 0.2, { autoAlpha:1 } ),
new AddEventListener( c.pigMC, MouseEvent.MOUSE_DOWN, playNoise ),
new AddEventListener( c.buttonPrevious, MouseEvent.MOUSE_DOWN, handlePrevious )
);
}

override public function createOutroCommand():Command
{
return new SerialCommand(0,
new RemoveEventListener( c.pigMC, MouseEvent.MOUSE_DOWN, playNoise ),
new RemoveEventListener( c.buttonPrevious, MouseEvent.MOUSE_DOWN, handlePrevious ),
new TweenMaxTo( c.pigBodyCopy, 0.1, { autoAlpha:0 } ),
new TweenMaxTo( c.bodyCopyBG, 0.1, { height:0, width:0, autoAlpha:0, easing:Strong.easeOut } ),
new TweenMaxTo( c.pigMC, 0.1, { autoAlpha:0 } ),
new TweenMaxTo( c.pigTitle, 0.1, { autoAlpha:0 } )
);
}

/* CUSTOM METHODS */
private function handlePrevious(e:MouseEvent):void
{
sceneManager.setScene( new Scene02 ( c ) );
}

private function playNoise(e:MouseEvent):void{
var soundChannel:SoundChannel = new SoundChannel();
soundChannel = new PigNoise().play();
}
/* END CUSTOM METHODS */
}
}
If you notice the repeating signature of a Scene class, the timeline functionality is controlled by createIntroCommand and createOutroCommand. Below those functions are any custom methods that the scene needs. There are ways this could be further packaged up, like an external class that contains all the helper functions. For now, this is a way to build a quick linear module or application without resorting to the timeline or to a more intensive solution such as a fully-fledged framework.

Wednesday, December 8, 2010

A Morsel on Flash Professional Projects in Flash Builder

Flash Builder is so very great to use. Such a time saver for the stuff I work on, especially in conjunction with subversion. Having a repository view of a remote SVN server, being able to update and commit, not to mention the ease of 'diffing' in the Eclipse SDK. Best thing ever.

Lock down FB and SVN and you will find varying project types to choose from: Flex Project, Flex Library Project, or Flash Professional Project. Please join me in considering the good and bad of the Flash Professional Project.

Here are the things I like and don't like about managing a Flash Professional Project from FB.

The Likes:
  • The .project configuration file
  • can be saved to SVN, so you can open all your resources quickly at later date
  • allows you to select .as files from within Flash Pro and have them open in FB for a better coding experience ( still better even thoughh Flash Pro now completes code)
The Dislikes:
  • The .project configuration file
  • because collaborators with whom you share the Flash Professional Project, since the .project file is generally only usable in one particular configuration, will find it distracting
  • Two environments (FB and Flash Pro) means 2x the path configuration. Yes, you have to path to libraries and swcs twice. Once in FB, and once in Flash Pro.
  • Frequent quickly appearing and then disappearing phantom publishing panels, due to the need for FB to remain synchronized with Flash Builder. I'm on Mac and this is one of the more disturbing issues.
  • The fact that you must create and save a .FLA as a precursor to starting a new Flash Professional Project, then associate the .FLA with the new Project, and then add the new document class back to the .FLA. Far better to have the .FLA generated through FB and configured according to initial project values.
Seems like the nays have it this time. I find it works better to use subclipse to manage FLAs without converting them into Flash Professional Projects. The only tiny caveat to this approach is that you must open the associated .as files directly from FB rather than via Flash Pro.

It remains to be seen what Flash Builder "Burrito" adds to this mix.

ASDocr to generate ASDoc HTML documentation

Rather than use the command line tool ASDoc that ships with the Flex SDK, I'm trying out ASDocr released on the Grant Skinner's site, gskinner.com.

It's handy, just be sure to update your Air to the latest 2.x version (presently 2.5.1) and be sure also to grab the latest ASDocr, as several are linked from gskinner.com

Wednesday, November 10, 2010

Practical Explorations of Catalyst Workflow, Pt 2

dFurther into Catalyst, here are some details on its workflow implementation:

Catalyst is procedurally similar to IDE-driven Flash Development. In particular:
  • Imported objects are cut and pasted without regard to underlying code in the process of assembling and producing usable components
  • Graphics in Catalyst are nameless and instance-less and may well be duplicated many times in random fashion in the process of assembling components, resulting in a code-behind of untold and unmanaged complexity (until the developer gets a hold if it, that is)
  • Much like Flash circa v3, you can paint yourself into a corner if your work process is too design-centric. You must have an architectural view of what you are creating and scale it in anticipation of the final structured product. If you do not start with this view, unless it is a very simple project, you may get into a jam.
  • While Catalyst is a presentation tool, development and usability duties fall within its boundaries as well. One example is the need to create transparent hit-areas for buttons; this is a matter of function, not display.
Following on this, Catalyst in its current 1.0 state is more hybrid tool than design platform. The workflow role that it encapsulates is akin to that of a Flash web designer in a pure IDE environment.

The user must have an awareness of the developers' needs as well as graphical manipulation and timeline skills. A team member utilizing Catalyst would need equal measures of feedback from both dev and creative teams to be effective. Furthermore, upon the Catalyst user is placed the expectation of creating hierarchical, logical structures almost entirely from the mouse and clipboard.

As version 2.0 aka Panini moves to release, it will be interesting to see what's next.

Monday, November 8, 2010

Flash Catalyst and Flash Builder workflow

More workflow explorations incorporating Adobe Flash Catalyst. Among the hopeful tests and discoveries, a stark word of caution is a rare gem. Here is one from an article authored by Adobe's Andrew Shorten (italics mine):

It is not possible to re-open a Flex project in Flash Catalyst once you've imported it into Flash Builder. (The product teams will investigate this option for a future release, but it will not be available in the first release of Flash Catalyst.) To overcome this limitation, consider the other workflows in this article, in particular workflow 3, which extends the approach used here to support iterative development.
Workflow 3 incidentally outlines the use of 'Compare Project With Version' > [previous version] as a means of reconciling different code versions. This represents great hardship in a round-trip and, in light of just how different the versions would be, is IMO not a viable solution.

Further on this, a detailed blog entry regarding 'catalyst jailbreak for flex developers' is exploratory and non-committal in tone as it explores Catalyst integration. Here is a further obstacle to a Catalyst workflow that extends from a different angle on the quote above:

An important note: There isn't a place to set the ID on Flash Catalyst components. When a designer converts artwork to a component and then exports a custom component with multiple text inputs, say a registration form, none of those text inputs contain ID's.
And further:

...when the design changes and it does, the one way Flash Catalyst generated code has no knowledge of your so called ID's. The design has changed and all or nearly all the components are anonymous again. So the developer has to manually find and add the ID to each text input, radio button, button, each time the design is updated etc.
While there are workarounds, there is not yet a clean workflow to utilize.

Flash Catalyst Panini, you are up next.

Friday, November 5, 2010

Flash Builder 4 Workflow from Catalyst to Flex

Skin vs SparkSkin Classes in the Flash Builder 4.1 SDK

Seems the difference between the two is that SparkSkin adds some extensions that would likely not be used in the creation of a custom Spark Skin. Therefore, you can pretty much just use the Skin class if you are developing a custom implementation.

Here are a couple of resources with limited info on the topic:
http://forums.adobe.com/thread/465734?tstart=0
http://unitedmindset.com/jonbcampos/2010/06/02/the-difference-between-skin-and-sparkskin/

Thursday, September 16, 2010

What to expect and not to expect from FXG in Fireworks CS5

Fireworks CS5 support of FXG includes the following graphical elements:

Fireworks CS5 to FXG 2.0 Direct Mapping Support:
  • Filter Mappings: Blur, Blur More , Inner Shadow and Drop Shadow;
  • Blend Mode Mappings: Normal, Layer, Multiply, Screen, Lighten, Darken, Difference, Add, Subtract, Invert, Alpha, Erase, Overlay, Hardlight, Colordodge, Exclusion, Hue, Saturation, Color, Luminosity, Colorburn and Softlight;
  • Gradient Mappings: Linear and Radial gradients; Mask Mappings: Alpha and Clipping masks;
(see a Fireworkszone.com article for more details on this)

Some weak points in the developer workflow that a product like FXG/Catalyst could help:
  • Image/asset optimization of static design to ensure
    • fidelity of vector assets is maintained
    • optimization of bitmap assets is ideal
    • portable and round-trippable file type
    • AND: created objects can be referenced directly in MXML
FXG thus far addresses only the second point to satisfaction.

Regarding point one, vector data is frequently lost. A previous example in this blog is a scenario where textures are employed in a FW png, and the png is exported for external use. The resultant FXG loses the textures entirely when they should be reproduced from pattern data in Fireworks and incorporated into FXG.

Moving to point three, portability and round-tripping are decent. Lacking is a return from Flash back to FW, which has been pledged as an advance for CS6, but for that you have to wait.

Finally, point four addresses the ability to build an FXG and then reference the baked-in assets from Flash Builder, using AS3 and MXML. This in my opinion is the great missed opportunity of FXG. The markup created in FXG gives none of the naming and referencing functionality you would expect.

Put plainly, I can take an entire multi-page FW CS5 file from design, ensure the layers are correctly named and organized, and then export it into a data-rich FXG, a format loaded with individual vector and bitmap data. Disappointingly, once I move that into Flash Builder there is no built-in means to explicitly reference the assets and manipulate them in AS3 or MXML. To do that, you have to open the FXG up and hand-edit it.

Here are some details from Adobe's own Adobe Flex 4 document entitled "Using FXG."

To convert this example to MXML graphics:
  • Remove the tag. This example does not contain any elements.

  • Remove the block.

  • Move the two Illustrator-specific namespaces to the application’s root tag:

    xmlns:ai="http://ns.adobe.com/ai/2008"
    xmlns:d="http://ns.adobe.com/fxg/2008/dt"
  • Remove the FXG-specific namespace:

    "http://ns.adobe.com/fxg/2008"
  • Add the “s:” prefix to all elements.

  • Because the “d” and “ai” namespace definitions are intact, you do not have to remove the attributes specific to these namespaces. The attributes type and userLabel, for example, appear on the tag from the original FXG file. To further simplify this example, though, you could remove them because Flex ignores anything in those namespaces.

    For example, this:

    Becomes this:

A startling omission? Yes, rather. That is a substantial defeciency of this format IMO and one that will hopefully (certainly) be addressed in the future.