We’ve often gone about the run-around method to find out how many bytes a particular class or package of Actionscript files adds to a compiled swf. In the pre CS4 days (unless there was something of which we were unawares) we often created a new AS3 fla, imported and declared only the classes to be tested, and then compared the swf filesize to that of an empty swf. Not a terribly friendly method.

I haven’t seen a better method documented anywhere. Though I’ve recently found that Flash CS4 will include a list of compiled classes and their corresponding byte counts when using Generate Size Report. The trick, however, is that you also have to use the publish settings to automatically export a swc.

Picture 4

Here’s an example of a list of compiled classes produced when both “Generate size report” and “Export SWC” are checked:

Picture 2

Of course this could have been expected. A swc is just a zipped up swf and catalog.xml, listing all the compiled classes in the swf. I’m not sure why Flash doesn’t have a better system (and a more obvious reporting system) in the first place, but this hidden perk certainly reinforces the sensical quirkiness of the IDE we’ve all come to love over the years. The more you love the quirks, the more the IDE will love you back.

We’re working on a site right now whose individual pages are comprised of a stack of separately embedded swfs. Long story short, most of the content loaded into these swfs is dependent on particular flashVar data. Since I have one collection of variables intended to be global (passed into every embedded swf), I’d created a global generic object to store those name-value pairs and then assigned individualized objects per swf to the global object (var localObject = globalObject; ) before assigning that swf its unique properties. What I didn’t realize was that the JavaScript, of course, wasn’t copying the globalObject but simply referencing it, so by the time the entire page was computed there was still only one object, it just contained all properties that were suppose to instead be set per swf embed.

What this led me to illustrate through firebug introspection and flash tracing once the swfs were loaded was that even if a generic object was set with properties to be passed into a swfobject embed as flashVars, and then passed into that swfobject embed, any changes to that generic object after the embed will be reflected by the swf once loaded. So if I type in Javascript:

var flashVarsObject = {};
flashVarsObject.foo = "bar";
swfobject.embedSWF("example.swf", "example", "400", "300", "9.0.0", "expressInstall.swf", flashVarsObject);

… the flash trace for stage.loaderInfo.foo would be “bar”.
But if, after the swfobject.embedSWF call, I further set the foo variable, such as:

var flashVarsObject = {};
flashVarsObject.foo = "bar";
swfobject.embedSWF("example.swf", "example", "400", "300", "9.0.0", "expressInstall.swf", flashVarsObject);
flashVarsObject.foo = "fighters";

… the flash trace would be “fighters”.

FlashVar name/value pairs are apparently not passed in upon calling embedSWF, but rather, the flashVar object appears to simply be referenced. This of course makes just as much sense, but what seemed to make it out of place was that using firebug, I could see the actual <object> and <param> tags rendered by swfobject. At the core, thats really all swfobject does. What is strange about it is that a <param> tag includes attributes for “name” and “value”, appearing as literal strings when rendered. Somehow, this param tag synchronously represents the flashVars object passed into embedSWF, even though the param appears to be simple strings. Keep an eye out for this little catch, and of course its best to keep JavaScript objects well separated for this sort of use.

Here’s a real quick JSFL trick. We all know that working with color transforms in code is cool, except that it’s usually easier to design a transform using the Color: Advanced setting in the Properties panel. Here ya go:

var s = fl.getDocumentDOM().selection[0];

fl.trace("new ColorTransform("
            + (s.colorRedPercent   / 100) + ", "
            + (s.colorGreenPercent / 100) + ", "
            + (s.colorBluePercent  / 100) + ", "
            + (s.colorAlphaPercent / 100) + ", "
            + (s.colorRedAmount  ) + ", "
            + (s.colorGreenAmount) + ", "
            + (s.colorBlueAmount ) + ", "
            + (s.colorAlphaAmount)
            +")");

Just select an object and run the script. You’ll get a trace in the Output panel with the equivalent ActionScript ColorTransform. If you like, you can even modify it like so:

fl.trace(s.name + ".transform.colorTransform = new ColorTransform("

If you’ll be applying this ColorTransform to the instance you selected, rather than using the selection as a temporary guide.

Ever make a symbol with the registration point in the, say, default top left corner, and then later realize you need it in the center for rotation or scaling purposes? But you’ve already placed the instance on stage where it needs to be, so not only do you have to move the symbol’s contents, you have to move the whole instance on stage by a complementary amount. Unless you have this script! Check it out:

fl.outputPanel.clear();

function run() {

    var e = fl.getDocumentDOM().selection[0];
    if (!e) return;

    var x = parseFloat(prompt("Enter new x:", e.x));
    if (isNaN(x)) return;

    var y = parseFloat(prompt("Enter new y:", e.y));
    if (isNaN(y)) return;

    fl.trace("Moving element from (" + e.x + ", " + e.y + ") to (" + x + ", " + y + ")");

    var diffX = e.x - x;
    var diffY = e.y - y;

    e.x = x;
    e.y = y;

    // Get the Timeline of the object in question.
    var tl = e.libraryItem.timeline;
    // Loop over the Timeline's Layers.
    var iLen = tl.layers.length;
    for (var i = 0; i < iLen; i++) {
        var l = tl.layers[i];
        fl.trace("Layer: " + l.name);
        // Loop over each Layer's Frames.
        var jLen = l.frames.length;
        for (var j = 0; j < jLen; j++) {
            var f = l.frames[j];
            // Don't mess with elements in frames that aren't keyframes...
            if (f.startFrame != j) continue;
            fl.trace("\tFrame: " + (j+1));
            // Loop over each keyframe's elements.
            var kLen = f.elements.length;
            for (var k = 0; k < kLen; k++) {
                // Move the element.
                var child = f.elements[k];
                fl.trace("\t\t" + child + " " + k + " :: " + child.name
                        + "("+child.x+","+child.y+") => ("+(child.x+diffX)+","+(child.y+diffY)+")");
                child.x += diffX;
                child.y += diffY;
            }
        }
    }
}

run();

Select an item (one item at a time, please, although it shouldn’t be hard to expand this to loop over every item in the selection, assuming you want to move a bunch of items by the same amount). You’ll be prompted for a new x and a new y. This is the x,y of the registration point of the instance. Plugging in new values that are, say, 100 pixels greater than the current values will move the symbol’s contents up and to the left by 100 pixels, while moving the instance down and to the right by 100 pixels. It even traverses the keyframes of each layer, so a timeline animation should get adjusted all at once.

A log of what happened will show up in your Output panel:

Moving element from (116, 148) to (216, 248)
Layer: Layer 3
    Frame: 1
        [object SymbolInstance] 0 :: (53.7,42.7) => (-46.3,-57.3)
Layer: Layer 2
    Frame: 1
        [object Shape] 0 :: (43.45,85.45) => (-56.55,-14.549999999999997)
Layer: Layer 1
    Frame: 1
        [object Shape] 0 :: (100.95,38.95) => (0.9500000000000028,-61.05)

It’s worked reasonably well for me so far, but this is one of those things where the logic is complicated enough to possibly fail in certain situations. If you run into such a situation, please let us know in the comments. I’d love to improve this script if it needs it.

We’ll just make this JSFL kick a mini-series. Here’s another handy script. I think I originally wrote this while working in Flash CS3, because Flash CS4 provides a “search in library” field. Even so, this can save a few steps. Simply select an instance on the stage, and run the script, and the instance’s symbol will be selected in the Library. One failing is that if the Library is not already showing, the script can’t do anything to open it. The item does get selected, though. You just need to open the Library manually.

// Get the objects we'll need to work with.
var doc = fl.getDocumentDOM();
var sel = doc.selection[0];
var lib = doc.library;

// Grab the name of the LibraryItem object of the current selection.
var libItem = sel.libraryItem.name;

// Take that name and split it into "crumbs."  If we have more than one
// crumb, the item is nested into a folder and we need to make sure
// the folder is expanded.
var path = libItem.split("/");
if (path.length > 0) {
    path.pop();
    var folderItem = path.join("/");
    lib.expandFolder(true, true, folderItem);
}

// Select the item.
lib.selectItem(libItem);

If anyone knows of a way to make sure the Library panel opens up, please leave a comment!

Continuing on the theme from my last post on a quick JSFL script, here’s another one that can be handy when working in an unfamiliar FLA with tonnes of layers:

var doc = fl.getDocumentDOM();
var prevTerm = doc.getDataFromDocument("layerSearchTerm");
if (!prevTerm) prevTerm = "";

var searchTerm = prompt("Search timeline layer names for:", prevTerm);
doc.addDataToDocument("layerSearchTerm", "string", searchTerm);

if (searchTerm) {
    var term = searchTerm.toLowerCase();
    var tl = doc.getTimeline();
    var lyrs = tl.layers;

    var layerIndex = tl.getSelectedLayers()[0];
    var startIndex = layerIndex;

    do {
        layerIndex++;
        if (layerIndex >= lyrs.length) layerIndex = 0;
        var lyr = lyrs[layerIndex];
        if (lyr.name.toLowerCase().indexOf(term) > -1) {
            tl.setSelectedLayers(layerIndex);
            break;
        }
    } while (layerIndex != startIndex);

    if (layerIndex == startIndex) {
        alert("Search term \"" + searchTerm + "\" not found.");
    }
}

This will prompt you for a search term, and will then proceed to search through the timeline layer names for that term. When it finds one, that layer will be selected. If you run the search again, you’ll see your previous term still in the field. Just hit “OK” and the search will continue from the currently selected layer (that is, the first match).

I found this guy useful a few times when opening up complex files that I haven’t touched in a while. The 20 minutes it took to write has probably paid for itself by now.

We’ve been neglecting our blog. For our regular readers, we apologize. We’ve been pretty busy lately, and while we should probably recount some of our recent adventures, I thought I’d kick things back in order with a quick post on a little JSFL script I wrote to help alleviate some pain in a recent task. Another win for automation; it took about 15 minutes to get the script right, and then proceeded to save me about 2 hours of manual work.

To use, first select a bunch of MovieClips on the stage, then run the script.

What will happen is that all of your clips will get named, starting with “item1” and incrementing the number until all items have been named.

More than that, though, it sorts the selection from left to right, so that

  1. the item with the left-most x value is “item1”, and the item with the right-most x value is “itemN
  2. ActionScript code is printed to the Output panel that creates an Array and lists all of the clips by name, in order from left to right.

It’s easily modifyable, especially if your familiar with Array.sort’s compareFunction. Here’s the script:

var sel = fl.getDocumentDOM().selection;

sel.sort(sortByX);
function sortByX(a, b) {
    if (a.x < b.x) return -1;
    else if (a.x > b.x) return 1;
    return 0;
}

fl.trace("var clips:Array = [");

var iLen = sel.length;
for (var i = 0; i < iLen; i++) {
    var e = sel[i];
    e.name = "item" + (i+1);
    fl.trace("\t" + e.name + (i == iLen-1 ? "" : ","));
}

fl.trace("]");

Example output:

var clips:Array = [
    item1,
    item2,
    item3,
    item4,
    item5,
    item6,
    item7,
    item8,
    item9,
    item10
]

I’ve been meaning to write this up for a while, and a recent post on a certain Flash forum prompted me to get ‘er done.

To make a long story short, a while back I was working on a projector to put on a DVD-ROM. Flash 8 was still fresh out of the oven, so it was before Apollo née AIR was even announced, and FileReference only worked with an http server at one end of the transfer. I needed to be able to copy file from the disc to the user’s hard drive, so FileReference wasn’t going to work, and in the end we bought Zinc to accomplish this, which turned out to be a $600 feature, and an absolute fright to deal with.

Unfortunately for me, I didn’t discover this until after the product shipped, but I learned that from a projector, you can use fscommand to run applications. fscommand is no longer just “the old way” to execute JavaScript! On Windows, you can execute .bat files. On the Mac, it’s AppleScript applets. Which means we can copy files around using AppleScript, and not Flash.

I haven’t seen any sort of details on how to do this, so that’s what this post is about.

I must admit at this point that I know nothing about .bat files on the PC. So this post will focus solely on AppleScript. But the idea is the same, just replace any reference to AppleScript with .bat file, and it should be pretty much the same.

  1. Create a new Flash file, and set its publish settings to produce a Macintosh projector, making note of where this projector will be saved.Fscommand Applescript 1: The Mac Projector
  2. Open up Script Editor, found in /Applications/AppleScript
  3. Write your script. If you need a sample, try this one: display dialog "It worked!"Fscommand Applescript 2: The AppleScript
  4. Save your script. Be careful to follow these directions exactly:
    1. In the “File Format” popup, change it from the default “Script” to “Application”
    2. Navigate to the folder where your Flash projector is saved.
    3. Click the “New Folder” button and name the folder “fscommand” (this has to be exact).
    4. Save your AppleScript app inside the fscommand folder.Fscommand Applescript 3: Saving the AppleScript
  5. Back in Flash, add an action somewhere. For this example, I’ll just throw it on the first frame script. It looks like this:
fscommand("exec", "test.app")

Obviously, if your AppleScript app is of a different name, then put that name in there.

“exec” will only work if the application to execute is in a folder called “fscommand” relative to the projector file.

Test the file by pressing Command-F12 to publish preview. You should see your script run.

The limitations:

  • No way to pass values to the script. If you have 10 files you want to transfer from a DVD to the hard drive, and 10 buttons to start transferring each one, then you need 10 AppleScript apps, one for each file. It would be great if you could just pass along parameters…
  • No way to get values back from the script. Same issue as before. It’s just a process that gets started and runs on its own.
  • No way to even know when the script has finished running. Bummer.

So, clearly not an ideal solution, but still a good trick to have in the tool box.

I’ll make this post nice and short. If you were intrigued by my post on integrating C code with JSFL, you may be interested in knowing that I finally A) made time to put together a C/JSFL project template for Xcode 3, and B) figured out just how the h___ you’re supposed to do that.

The template may be downloaded here.

It contains the necessary mm_jsapi.h header file, all of the appropriate build settings, and a template main.c file that gets you all ready to go, complete with that good old computeSum() function stubbed in for ya.

If you’ve never installed a custom Xcode template before, you may be tempted to search the Internet. I would advise you, do not do so, for most search results on phrases involving “Xcode,” “template”, and optionally “install” will contain information telling you to put your template into /Developer/Library/. This is advised against by Apple, as that’s where they put their built-in templates. Custom templates should go in either:
~/Library/Application Support/Developer/Shared/Xcode/Project Templates
or
/Library/Application Support/Developer/Shared/Xcode/Project Templates

The first path will make the template available only to your user account, the second will make it available to all users on the machine. (This post does rank high on Google, and contains the correct information)

Also note that if you have not installed any custom templates yet, you may need to create one or more folders in the install path.

I actually updated to 10.0.2 two weeks ago, and have had this video capture sitting on my desktop patiently awaiting upload. We’ve had a little downtime lately, so here you go. Maybe you saw this, too. The Adobe Updater is already pretty ridiculous, but this takes the cake (for me, at least):

It did that the whole time. Almost like…it was built in Flash and someone forgot to put a stop() command on the first frame…

Topics

Archives