Files needed for the tutorial

One of the coolest things about developing AIR applications is the complete control you have over the way the launched application is styled. You can control everything – even down to the chrome (the window surrounding the application screen). I recently did a project where the client requested a custom chrome. I developed a pretty snazzy workflow using library assets pulled from a compiled .swc file – here’s how I did it.

Step 1 – Create a new Flex project within Flex Builder, making sure to set the Application Type as ‘Desktop Application’ (this tells the compiler to build the project as an AIR application instead of a standard .swf file).

Step 2 - Edit the application’s XML file. Flex Builder creates a few folders within your project – you should see folders for ‘bin-debug’ , ‘libs’, and ’src’. Within the source folder there is the blank mxml file, started with just a windowed application tag, and an XML file ‘yourProjectName-app.xml’. All AIR applications built within Flex builder draw from an XML file that declares basic program parameters.

Open up the XML file and find the following lines:

<!– The type of system chrome to use (either “standard” or “none”). Optional. Default standard. –>
<!– <systemChrome></systemChrome> –>

<!– Whether the window is transparent. Only applicable when systemChrome is none. Optional. Default false. –>
<!– <transparent></transparent> –>

Flex has commented their code nicely, so it’s pretty clear how these tags need to change. Uncomment the lines with tags in them, and set their values so your code looks like:

<!– The type of system chrome to use (either “standard” or “none”). Optional. Default standard. –>
<systemChrome>none</systemChrome>

<!– Whether the window is transparent. Only applicable when systemChrome is none. Optional. Default false. –>
<transparent>true</transparent>

Step 3 – Format the base WindowedApplication tag

When you open the application’s main .mxml file, you’ll see the WindowedApplication tag looking like this:

<mx:WindowedApplication xmlns:mx=”http://www.adobe.com/2006/mxml” layout=”absolute”>

One primary change needs to be made to this class – the ’showFlexChrome’ attribute should be set to false.  So your WindowedApplication tag should now read:

<mx:WindowedApplication
xmlns:mx=”http://www.adobe.com/2006/mxml”
layout=”absolute”
showFlexChrome=”false”
>

Now try compiling and running your application. You see the adl program launch (that’s how Flex compiles and runs AIR applications in the development perspective), but nothing happens. Actually, it’s not that nothing is happening – it’s that we’ve eliminated every visual component from the screen (including the chrome), so there’s nothing to see. Try adding a button to the stage and recompiling – you should see a weird zombie button floating in the middle of your screen. Weird. Let’s give this puppy some chrome.

Step 4 – Add the chrome to the application

The chrome for our application is laid out in the Flash file in the libs folder. If you look in the Flash file you’ll notice a single element on stage – an MovieClip instance named ‘Header’. Navigate into this and you’ll see a few elements broken out into layers – there are jewel buttons for handling the close, minimize, and maximize functions, a title, and a background MovieClip named ‘Chrome’. This is the header of our application.

Compile the .swf, making sure to select the ‘Export SWC’ option in the Publish Settings, and place the resulting .swc into the classpath of your Flex project. For convenience’s sake, I added mine to the libs folder (it’s added to the classpath by default).

Adding the chrome functionality to the application is then a process of adding actionscript to the project that correctly references these MovieClip elements and responds appropriately to user interaction. Add the following code block to your application:

<mx:Script>
<![CDATA[
import mx.core.UIComponent;
import com.echobloom.testAIRApp.Header;

private var header:Header;
private var redJewel:MovieClip;
private var yellowJewel:MovieClip;
private var greenJewel:MovieClip;
private var chrome:MovieClip;
private var border:Shape;
private var background:Sprite;

private var _maximizeState:Boolean = false;  // State of maximization (so you can toggle between full and normal views)

private function init():void {
createDisplayElements();
addEventListeners();
}

private function createDisplayElements():void {
var component:UIComponent = new UIComponent();  // The root stage element of all elements on screen that can be interacted with

header = new Header();  // The header pulled from our compiled .swc

background = new Sprite();  // A sprite serving as the background of our application screen
background.graphics.beginFill(0xEEEEEE, 1);
background.graphics.drawRect(0, 0, 600, 400);
background.graphics.endFill();

border = new Shape();  // The border around the application screen
border.graphics.lineStyle(1, 0x000000);
border.graphics.lineTo(background.width - 1, 0);
border.graphics.lineTo(background.width - 1, background.height - 1);
border.graphics.lineTo(0, background.height - 1);
border.graphics.lineTo(0, 0);

redJewel = header.getChildByName("redJewel_mc") as MovieClip;  // The close button
redJewel.useHandCursor = true;
redJewel.buttonMode = true;

yellowJewel = header.getChildByName("yellowJewel_mc") as MovieClip;  // The minimize button
yellowJewel.useHandCursor = true;
yellowJewel.buttonMode = true;

greenJewel = header.getChildByName("greenJewel_mc") as MovieClip;  // The maximize button
greenJewel.useHandCursor = true;
greenJewel.buttonMode = true;

chrome = header.getChildByName("chrome_mc") as MovieClip;  // The background element of the header (used to handle application dragging)
chrome.useHandCursor = true;
chrome.buttonMode = true;

component.addChild(background);
component.addChild(header);
component.addChild(border);
addChild(component);
}

private function addEventListeners():void {
redJewel.addEventListener(MouseEvent.CLICK, handleCloseClick);
yellowJewel.addEventListener(MouseEvent.CLICK, handleMinimizeClick);
greenJewel.addEventListener(MouseEvent.CLICK, handleMaximizeClick);
chrome.addEventListener(MouseEvent.MOUSE_DOWN, dragStart);
}

// Close button Action
private function handleCloseClick(e:MouseEvent):void {
var exitingEvent:Event = new Event(Event.EXITING, false, true);
NativeApplication.nativeApplication.dispatchEvent(exitingEvent);
if (!exitingEvent.isDefaultPrevented()) {
NativeApplication.nativeApplication.exit();
}
}

// Minimize button action
private function handleMinimizeClick(e:MouseEvent):void {
stage.nativeWindow.minimize();
}

// Maximize button action
private function handleMaximizeClick(e:MouseEvent):void {
stage.nativeWindow.maximize();

doMaximize(!_maximizeState);  // Toggle the maximizeState
}

// Dragging the application
private function dragStart(event:MouseEvent):void {
stage.nativeWindow.startMove();
}

// Changing the size of the header, background, and border
private function doMaximize(maximizeState:Boolean):void {
this._maximizeState = maximizeState;

if(_maximizeState) {
header.chrome_mc.width = stage.fullScreenWidth;

background.graphics.clear();
background.graphics.beginFill(0xEEEEEE, 1);
background.graphics.drawRect(0, 0, stage.fullScreenWidth, stage.fullScreenHeight);
background.graphics.endFill();
} else {
header.chrome_mc.width = 600;

background.graphics.clear();
background.graphics.beginFill(0xEEEEEE, 1);
background.graphics.drawRect(0, 0, 600, 400);
background.graphics.endFill();
}
border.graphics.clear();
border.graphics.lineStyle(1, 0x000000);
border.graphics.lineTo(background.width - 1, 0);
border.graphics.lineTo(background.width - 1, background.height - 1);
border.graphics.lineTo(0, background.height - 1);
border.graphics.lineTo(0, 0);
}

]]>
</mx:Script>

Step 5 – Wire the WindowedApplication tag to call the init method. Now that we’ve got everything setup, we need to call our init() method when the application has finished loading. The WindowedApplication tag should now look like this:

<mx:WindowedApplication xmlns:mx=”http://www.adobe.com/2006/mxml”
xmlns:local=”com.adobe.views.*”
layout=”absolute”
width=”600″
height=”400″
showFlexChrome=”false”
applicationComplete=”init()”
xmlns:display=”flash.display.*”>

That’s it!

Food Flinger - Screenshot 1I’m happy to announce the most recent Echo Bloom code project – a flash game I wrote for Kid Bombay titled Food Flinger – is now live. Here’s how the project came to life.

A detailed gameplay spec was provided by the client at the onset of the project, allowing us a nice framework to work within. The spec described how the game would be played, how passed items and powerups would behave, and gave suggestions toward the art direction. Using this spec as a roadmap, I implemented the gameplay and base functionality inside a ‘block world’ – a representation of the game using geometric primitives (as opposed to the shiny graphics that would make up the final product). It was uber-simple, but it showed how key components of the game should interact, and gave us a model to tinker around with and see how the spec ‘felt’ when realized.

While the block world was coming into existence, Mike Daley, an excellent designer/animator in the Bay Area, set to work designing the visual elements of the game. He started with raw sketches, moved into flat graphic assets, and then into fleshed out animations. Once his characters were done, the functionality in the block world had evolved to a point where the shiny animations could be substituted for the flat blocks, allowing the game to spring to full featured animation quickly.

As the game quickly became something real, we had several weeks of tweaks and refinements. By far, the most challenging component of the game was the passing motion of the individual characters. Dynamically adding assets to the animations (e.g. adding a football to a hand as it makes a passing motion) was pretty straightforward – the challenging part came was that the animations had to be dynamically sped up and slowed down. The animations were library elements imported as MovieClips into the game’s architecture – and there’s unfortunately no playAtSpeed property for MovieClips. We circumvented this issue by using the frame tweening in TweenLite. It took a few days of tinkering with the timing, but we eventually got everything to line up.

LESSONS

This was my first soup to nuts game implementation, and I learned a lot along the way:

1 – Programming within a tightly constrained design pattern *really* helps. Things are predictable and extensible, allowing other people to simultaneously work on specific elements of functionality without collisions. For this game, I used a tightly defined Model-View-Controller implementation.

2 – Issues surrounding memory management that once sounded academic became a lot more practical when developing a game that had so many complicated assets. There are a lot of resources describing how programs can be optimized to better deal with this – Grant Skinner’s got an excellent summation and Colin Moock’s AS3 Bible has some more information.

3 – Getting things to work is one thing, getting them to feel right is another. A week before release we were 95% of the way on code, but were far further back in polish and playability. Ketan Anjaria, the principal at Kid Bombay, added much of the polish that took the game from functional to fun – sound, effect animations, and level balancing made the game dynamic, and fun to play.

Take a look, and let me know what you think. I’ve started looking into iPhone games, and general development on mobile platforms. The medium is the message, and a mobile context offers a radically different environment for making and playing games. Who knows? Maybe I’ll work on an iPhone application in 2010 – stay tuned.

The description of Project Nevins can be found here – much more Daniel Nevins at DanielNevins.com.

The average computer audio player (e.g. iTunes, Real Player, etc.) is the digital equivalent of a Walkman. Given the possibilities inherent in operating on powerful web-ready computers, these programs are oddly content to parrot only the basest functionality of analog technology. They are, as McLuhan would note, the media of the new doing the work of the old.

Music is an inherently social pursuit – we experience concerts together, find out about new music from friends, and discuss specific parts of songs with people while they’re playing. Web 2.0 technology is perfect to enable this type of social interaction with audio, but the that currently exist merely plunder the internet for commercial aims. I started Project Nevins with the charge of making an efficient Web 2.0 audio player. It’s now the leading edge of a system that will enable new ways of interacting with audio content:

Here are the system’s fundamental components:

Playspace – What I refer to as the playspace, or the section the playhead traverses as an audio file is played, has typically been a black hole of audio interface designs. With Project Nevins, the playspace becomes the palette for users to socially annotate audio files.

Comments – Comments are the circles that appear on the playspace. They come in two flavors – administrator comments are added by the creator of the content, and user comments can be added by anyone.

Contextual Menu – How many times have you blindly scrolled through an audio file looking for a specific section of material? Audio needs an inline table of contents – contextual menus are a start.

I see this system as having a few initial applications. Independent musicians could use the interface to directly communicate with their fans about their music, like an interactive track guide. Teachers could use the interface to point out specific sections of an audio piece, and students could respond (far easier than referencing a track by the time elapsed). Bands could trade arrangements back and forth, using the comments to talk about specific things they changed, or parts they feel need work.

I was assisted in this venture by the inordinately gifted Asheville painter Daniel Nevins, whose work might best fall into the ‘magical realist’ genre. The seed content for this interface comes from a radio piece I assembled after a series of interviews with Daniel earlier this year.