My first Adobe Air Application

To help improve my AS3 skills I thought a good starting project would be to challenge myself to create an Adobe Air desktop application, and after working on a Christmas countdown, I decided it would be rather nice to make a Countdown timer.

This article runs through a few things I had to learn in AS3 to create it.

Getting Adobe Air working

First thing I needed to do was ensure that Adobe Air was installed and that everything working.

  • Install Adobe Air
  • Ensure Flash is updated, and install all the available updates. For Flash CS4 they are here.
  • Get the Adobe AIR SDK. Once downloaded this needs to be copied into the AIK folder in the Flash program folder – i.e. C:\Program Files\Adobe\Adobe  Flash  CS4\AIK.

Phew! Now creating the application…

First step was to get an accurate countdown mechanism. I found this Countdown example which was a good starting point to create a Countdown class in AS3. The basic idea is that it takes the current date and the specified event date then works out the difference.

Creating the event date input

I used Flash UI components for the drop down menus, but controlled their content using my Countdown class. To style the UI combo box component you can edit it in the FLA file, but to change the font you need create a TextFormat variable, then apply it to the component.

var comboFont : TextFormat = new TextFormat ();
comboFont.font = "Calvert MT Light";
comboFont.color = 0x333333;
comboFont.size = 12;
eventDayCombo.textField.setStyle ("embedFonts", true);
eventDayCombo.textField.setStyle ("textFormat", comboFont);
eventDayCombo.dropdown.setRendererStyle ("embedFonts", true);
eventDayCombo.dropdown.setRendererStyle ("textFormat", comboFont);

I then added data to the combo box. It is important to add this data dynamically to ensure that the Countdown is always up to date.

eventDayCombo.addItem ({label: dayStr, data: day})

Reading, saving and using XML data

Check if the data exists

Every time the application opens it checks to see if the user has already set an event date. The first thing it does is specify the name of the file, then where this file should be located. In my Countdown example the application creates an XML file called CountdownPrefs in the user’s My Documents folder.

var prefs:String = "CountdownPrefs.xml";
var prefsFile:File = File.documentsDirectory.resolvePath (prefs);

To check if a file exists on the system you can use prefsFile.exists; this will give a true or false result.

if (prefsFile.exists){

AS3 uses file streams to manipulate data; this makes the process much simpler than in AS2. It opens a file stream and reads the data. When it has completed reading the data the event listener calls the function processXMLData.

readStream = new FileStream ();
readStream.addEventListener (Event.COMPLETE, processXMLData);
readStream.openAsync (prefsFile, FileMode.READ);
}

Processing the data

So, to process the XML data I just use the function to specify the data then close the file stream. Now I can set my event date from the data in the XML file.

function processXMLData (event : Event) : void {
var prefsXML: XML = XML (readStream.readUTFBytes (readStream.bytesAvailable));
readStream.close ();
TITLE = prefsXML.child ("title");
YEAR = prefsXML.child ("year");
MONTH = prefsXML.child ("month");
DAY = prefsXML.child ("day");
HOUR = prefsXML.child ("hour");
MINUTE = prefsXML.child ("minute");
startCountdown ();
}

Saving new data

When the user sets their event date in the application it creates an XML file. First I defined what the content of the XML should be.

var dateStr:String = "<?xml version=\"1.0\" encoding=\"utf-8\"?><countdown><title>" + TITLE + "</title><year>" + YEAR + "</year><month>" + MONTH + "</month><day>" + DAY + "</day><hour>" + HOUR + "</hour><minute>" + MINUTE + "</minute></countdown>";

It then opens a file stream and writes the data to the location specified previously using the string variable to construct the XML content.

var newStream:FileStream = new FileStream();
newStream = new FileStream();
newStream.open(prefsFile, FileMode.WRITE);
newStream.writeUTFBytes(dateStr);
newStream.close();

Delete the data

To reset the countdown application all you need to do is simply delete the XML file then I called my reset function to restart the application.

if (prefsFile.exists){
prefsFile.deleteFile ();
resetCountdown ();
}

Using the Timer Class

So my Countdown function returns an object with the years, months, days, hours, minutes, seconds and milliseconds, then to update my countdown display I need to repeat the call every millisecond.   I can use the Timer class to repeat the function call and get the updated countdown object.

timerObject:Timer = new Timer (delay, repeatCount );
timerObject.addEventListener (TimerEvent.TIMER, updateCountdown);
timerObject.start ();

The finishing touches

Creating a custom chrome

To make the application visually nicer I added my own chrome to it with minimise and close buttons, here is how: http://www.adobe.com/devnet/air/flash/articles/custom_chrome_app.html

Creating an application icon

This is very simple. All you need to do is create little image files in the sizes: 128×128, 48×48,32×32, 16×16 then on the publish settings you can add the icon images.

Publishing the application

All you have to do then is publish the application and it’s ready to go. Look out for my next post if you want to download the application and try it out.

Creating Buttons with ActionScript 3.0

This is a quick guide on how to create and set up a button in AS3.

Set up the movie clip as a button

To make sure that your movie clip will act like a button you need to set the buttonMode and also set the movie clip to have a hand cursor.

bttn.buttonMode = true;
bttn.useHandCursor = true;

Ensure that you won’t interact with the child movie clips

When you rollover a movie clip in AS3, then the mouse cursor will recognise child clips within your button movie clip. To ensure that this doesn’t happen you can set the mouseChildren to false.

bttn.mouseChildren = false;

Making the button do stuff

Create a event listeners to call a function when the you interact with the movie clip.

bttn.addEventListener (MouseEvent.MOUSE_DOWN, doStuff);
bttn.addEventListener (MouseEvent.MOUSE_OVER, overBttn);
bttn.addEventListener (MouseEvent.MOUSE_OUT, outBttn);

Using mouse events

Here is a sample of how you use the mouse events. This function will trace the movie clip that you have clicked on.
function doStuff (evt : MouseEvent) : void
{
trace(evt.target)
}

Damn you, Myriad!

I don’t know if I’m the only one who’s getting annoyed because whenever I start a new file I have to constantly change the font from Myriad. Just because it’s the Apple corporate font, doesn’t mean we all want to use it ALL THE TIME.

So, anyway, apparently you can change it – and here is how:

  • 1. Open up Illustrator CS3
  • 2. Go to File > Open, and open up: C:/Documents and Settings/{Username}/Application Data/Adobe/Adobe Illustrator CS3 Settings/New Document Profiles. You may have to go to your folder options first to make sure you can see hidden folders (Open up a folder window, Go to Tools > Folder Options, then click on the ‘View’ tab and select ‘Show hidden files and folders’)
  • 3. Open up one/any/all of the default document templates, depending what you use.
  • 4. Go to Window > Type > Character Styles
  • 5. Double click on ‘Normal Character Styles‘ in the ‘Character Style Options‘ panel
  • 6. Then click on ‘Basic Character Formats
  • 7. Change the options to whatever you want
  • 8. Save and close the file

So now whenever you create a new file using the template you changed, it will have your font options as default.

Flash Tutorial – Arranging a scene (Part Two) using hitTest

In the first tutorial (Arranging a scene using depths & scale) I created a scene that gives the impression that a character can move around scenery using the _y property. This tutorial explains how to stop the character moving through the scenery using hitTest.

First, create the file from the first tutorial.

In the Scene

Create a new movie clip and add a rectangular shape in it. Place this movie clip in each of your character and scenery movie clips, re-sizing it to the width of the item and the height to represent the “depth” – see diagram. Name each of the rectangular movie clips “testarea” and set the alpha to 0%.

Flash tutorial diagram

Initial Variables

Add this below the intial variables in the Actions frame.

moveAllowed = true;

Additional Code

In the mouseListener function, replace the code

_root.Character._x = _root._xmouse;
_root.Character._y = _root._ymouse;

with this code

if (_root.moveAllowed) {
_root.Character._y = _root._ymouse;
_root.Character._x = _root._xmouse;
} else {
_root.Character._x = _root._xmouse;
}
for (i=0; i<DepthArray.length; i++) {
if (_root.DepthArray[i].mcname != "Character") {
if (_root.Character.testarea.hitTest(_root[DepthArray[i].mcname].testarea)) {
_root.moveAllowed = false;
break;
} else {
_root.moveAllowed = true;
}
}
}

And so…

Here are the results

Flash Tutorial – Arranging a scene using depths & scale

This tutorial explains how to use ActionScript to update depths and scales to give the impression that a character can move around scenery using the _y property.

First of all create your graphics and place them where you want them on the scene, giving them individual names. Ensure that the registration point is set to the bottom of the movie clip, this is especially important if your movie clips are different heights.

Next, create a new layer. This is where all the actions will go.

Initial variables

The first actions will hide the mouse cursor and state the initial variables; these will set the increments of the scale and y position.

Mouse.hide();
MinScale = 50;
MaxScale = 100;
ScaleIncrements = (MaxScale-MinScale)/100;
MinPos = 10;
MaxPos = 250;
PosIncrements = (MaxPos-MinPos)/100;

Setting the scene

Then, create an array to store all depth and height information on all the movie clips on the scene. This is a multidimensional array that stores the movie clip name, it’s _y position and the current depth.

var DepthArray:Array = new Array();
DepthArray.push({mcname:"Scenery_1", mcposition:Scenery_1._y, mcdepth:Scenery_1.getDepth()});
DepthArray.push({mcname:"Scenery_2", mcposition:Scenery_2._y, mcdepth:Scenery_2.getDepth()});
DepthArray.push({mcname:"Scenery_3", mcposition:Scenery_3._y, mcdepth:Scenery_3.getDepth()});
DepthArray.push({mcname:"Character", mcposition:Character._y, mcdepth:Character.getDepth()});

Next, the array should be sorted based on the y position. The property “Array.NUMERIC” is used to ensure the sort is ordering the array as a number rather than a string.


DepthArray.sortOn(["mcposition"], Array.NUMERIC);

Then the movie clips on the scene should be updated. This uses a for loop which will go through all the items in the array, and update the movie depth according to the order they have been placed into within the array, then scale the movie clips according to their y position so the movie clips that are higher will be smaller. This uses the variables that have been set at the beginning.


for (i=0; i<DepthArray.length; i++) {
// sort initial depths
DepthArray[i].mcdepth = i;
_root[DepthArray[i].mcname].swapDepths(DepthArray[i].mcdepth);
// set inital scales
CurrentPosition = (_root[DepthArray[i].mcname]._y-MinPos);
CurrentPositionPerc = CurrentPosition/PosIncrements;
_root[DepthArray[i].mcname]._xscale = _root[DepthArray[i].mcname]._yscale=MinScale+((CurrentPositionPerc)*ScaleIncrements);
}

Time to get moving

So now a mouse listener is added to allow the scene to be updated you move the character around.


var mouseListener:Object = new Object();
mouseListener.onMouseMove = function() {

The mouse cursor has already been hidden as part of the initial actions, so now the character must be set to follow the x and y positions of the mouse.


_root.Character._x = _root._xmouse;
_root.Character._y = _root._ymouse;

The array must be updated to set the character at the right depth, and then the scene is updated according to this.


for (i=0; i<DepthArray.length; i++) {
DepthArray[i].mcposition = _root[DepthArray[i].mcname]._y;
}
DepthArray.sortOn(["mcposition"], Array.NUMERIC);
for (i=0; i<DepthArray.length; i++) {
DepthArray[i].mcdepth = i;
_root[DepthArray[i].mcname].swapDepths(DepthArray[i].mcdepth);
}

Now the correct scale for the character is set.


CurrentPosition = (_root.Character._y-MinPos);
CurrentPositionPerc = CurrentPosition/PosIncrements;
_root.Character._xscale = _root.Character._yscale=MinScale+((CurrentPositionPerc)*ScaleIncrements);
};

Finally you just activate the listener


Mouse.addListener(mouseListener);

And so…

So, now just take a look at the final results.