AS3 & Flash


26
Aug 09

Pixel-Perfect Collision Detection Revisited

I've finally gotten around to updating my pixel-perfect collision detection post (which is by far the most popular post on this blog with 48+ comments and a double-digit percentage of all my pageviews). Of course, now that I've gotten around to updating it there's not much point, as others have improved upon and trumped what I had. So, I'll just provide a quick recap of their pursuits (and my version of them).

First, The Actionscript Man grabbed my version, Grant Skinner's (which mine was indirectly based on), digested them, then spit out a new one that eliminated some draw calls. He even made a nice little app that lets you toggle between the various versions and compare them. The only drawback of his version is that it assumes the two objects have the same parent.

Next, the venerable Tink posted his version that uses the same technique as The Actionscript Man but factors a more complicated parent-child relationship. Tink's still assumes that both objects are on the stage. His code also gets bitten by a bug I've run into related to localToGlobal and nested SWFs (though I still haven't explored it enough to write about it yet), as discussed in the comments. Tink added an "accuracy" parameter that basically scales the bitmap used for testing up and down. I've not found this makes enough of a performance difference to warrant the dramatic differences it can make in a pixel-perfect test.

So, I updated my version to include the rendering method Tink and The Actionscript Man used, and included the "common parent" math my original one had. The upside of my version is that it doesn't care whether your objects are on the stage or not, just that they share a common parent.

CODE:
  1. /** Get the collision rectangle between two display objects. **/
  2. public static function getCollisionRect(target1:DisplayObject, target2:DisplayObject, commonParent:DisplayObjectContainer, pixelPrecise:Boolean = false, tolerance:int = 255):Rectangle
  3. {
  4.     // get bounding boxes in common parent's coordinate space
  5.     var rect1:Rectangle = target1.getBounds(commonParent);
  6.     var rect2:Rectangle = target2.getBounds(commonParent);
  7.    
  8.     // find the intersection of the two bounding boxes
  9.     var intersectionRect:Rectangle = rect1.intersection(rect2);
  10.    
  11.     // if not pixel-precise, we're done
  12.     if (!pixelPrecise) return intersectionRect;
  13.    
  14.     // size of rect needs to be integer size for bitmap data
  15.     intersectionRect.x = Math.floor(intersectionRect.x);
  16.     intersectionRect.y = Math.floor(intersectionRect.y);
  17.     intersectionRect.width = Math.ceil(intersectionRect.width);
  18.     intersectionRect.height = Math.ceil(intersectionRect.height);
  19.    
  20.     // if the rect is empty, we're done
  21.     if (intersectionRect.isEmpty()) return intersectionRect;
  22.    
  23.     // calculate the transform for the display object relative to the common parent
  24.     var parentXformInvert:Matrix = commonParent.transform.concatenatedMatrix.clone();
  25.     parentXformInvert.invert();
  26.     var target1Xform:Matrix = target1.transform.concatenatedMatrix.clone();
  27.     target1Xform.concat(parentXformInvert);
  28.     var target2Xform:Matrix = target2.transform.concatenatedMatrix.clone();
  29.     target2Xform.concat(parentXformInvert);
  30.    
  31.     // translate the target into the rect's space
  32.     target1Xform.translate(-intersectionRect.x, -intersectionRect.y);
  33.     target2Xform.translate(-intersectionRect.x, -intersectionRect.y);
  34.    
  35.     // combine the display objects
  36.     var bd:BitmapData = new BitmapData(intersectionRect.width, intersectionRect.height, false);
  37.     bd.draw(target1, target1Xform, new ColorTransform(1, 1, 1, 1, 255, -255, -255, tolerance), BlendMode.NORMAL);
  38.     bd.draw(target2, target2Xform, new ColorTransform(1, 1, 1, 1, 255, 255, 255, tolerance), BlendMode.DIFFERENCE);
  39.    
  40.     // find overlap
  41.     var overlapRect:Rectangle = bd.getColorBoundsRect(0xffffffff, 0xff00ffff);
  42.     overlapRect.offset(intersectionRect.x, intersectionRect.y);
  43.    
  44.     return overlapRect;
  45. }

Finally, if you really want some sophisticated, fully-featured, pixel-perfect collision detection *and* handling, check out Corey O'Neil's Collision Detection Kit. I haven't used it yet, but it looks pretty comprehensive with good performance.


26
Aug 09

Quick fix for scale-9 issues

I discovered last week that scale-9 grids only apply to a DisplayObject and its content, but not its children. At first, I thought, "what!? why not!? that's dumb!" But, after a bit a reflection I realized that it would be *very* tricky to cascade a scale-9 transformation down a display list. I'm not going to attempt to explain why, but just imagine the scenarios where child DisplayObjects straddle the scale-9 grid and you'll realize it quickly moves into "very hard" (and likely slow) territory.

For my scenario, though, the child DisplayObjects simply provided some runtime configurability that once set could be "baked in." This allows you to take the shortcut of flattening the display list to a single bitmap, then applying a scale-9 grid to that. Unfortunately, the Flash Player doesn't allow you to set a scale-9 grid at runtime, probably due to some pre-calculations it does or special structuring of the data.

Searching for answers along these lines I ran across this fantastic post from Ahmed Nuaman:

Scale Any DisplayObject with My ‘ScaleObject’ Class

His theory was dead-on: take a DisplayObject, render it to a bitmap (to flatten it), cut that bitmap into 9 pieces, then override your new class's width/height properties to apply the scale-9 math to the individual pieces.

What I didn't really like was the roundabout way he solved the problem. First of all, the code was far too verbose for my tastes. It also wasn't self-contained, relying on an external bitmap utility function. But most problematic was the use of shapes with bitmap fills: I may be wrong, but I don't see any advantage to this, and it has the distinct disadvantage of being more complex that straight bitmaps and likely less efficient internally in the Flash Player. So, I fixed all that.

CODE:
  1. class ScaleObject extends Sprite
  2. {
  3.     /** Scale9grid pieces. **/
  4.     private var _tl:Bitmap;
  5.     private var _tc:Bitmap;
  6.     private var _tr:Bitmap;
  7.     private var _ml:Bitmap;
  8.     private var _mc:Bitmap;
  9.     private var _mr:Bitmap;
  10.     private var _bl:Bitmap;
  11.     private var _bc:Bitmap;
  12.     private var _br:Bitmap;
  13.    
  14.     /** Constructor. **/
  15.     public function ScaleObject(original:DisplayObject, scaleGrid:Rectangle)
  16.     {
  17.         _tl = slice(original, 0, 0, scaleGrid.left, scaleGrid.top);
  18.         _tc = slice(original, scaleGrid.left, 0, scaleGrid.width, scaleGrid.top);
  19.         _tr = slice(original, scaleGrid.right, 0, original.width - scaleGrid.right, scaleGrid.top);
  20.        
  21.         _ml = slice(original, 0, scaleGrid.top, scaleGrid.left, scaleGrid.height);
  22.         _mc = slice(original, scaleGrid.left, scaleGrid.top, scaleGrid.width, scaleGrid.height);
  23.         _mr = slice(original, scaleGrid.right, scaleGrid.top, original.width - scaleGrid.right, scaleGrid.height);
  24.        
  25.         _bl = slice(original, 0, scaleGrid.bottom, scaleGrid.left, original.height - scaleGrid.bottom);
  26.         _bc = slice(original, scaleGrid.left, scaleGrid.bottom, scaleGrid.width, original.height - scaleGrid.bottom);
  27.         _br = slice(original, scaleGrid.right, scaleGrid.bottom, original.width - scaleGrid.right, original.height - scaleGrid.bottom);
  28.     }
  29.    
  30.     /** Create a slice. **/
  31.     private function slice(original:DisplayObject, x:Number, y:Number, width:Number, height:Number):Bitmap
  32.     {
  33.         // render a slice of the original
  34.         var bd:BitmapData = new BitmapData(width, height, true, 0);
  35.         bd.draw(original, new Matrix(1, 0, 0, 1, -x, -y));
  36.        
  37.         // create and position the bitmap
  38.         var bitmap:Bitmap = new Bitmap(bd, PixelSnapping.AUTO, true);
  39.         bitmap.x = x;
  40.         bitmap.y = y;
  41.         addChild(bitmap);
  42.        
  43.         return bitmap;
  44.     }
  45.    
  46.     /** Width. **/
  47.     override public function set width(value:Number):void
  48.     {
  49.         _tc.width = _mc.width = _bc.width = value - _tl.width - _tr.width;
  50.         _tr.x = _mr.x = _br.x = value - _tr.width;
  51.     }
  52.    
  53.     /** Height. **/
  54.     override public function set height(value:Number):void
  55.     {
  56.         _ml.height = _mc.height = _mr.height = value - _tl.height - _bl.height;
  57.         _bl.y = _bc.y = _br.y = value - _bl.height;
  58.     }
  59. }

NOTE: The width and height properties specifically don't call the super setters. Doing so causes Sprite to attempt and scale its contents by adjusting scaleX and scaleY.


22
May 09

Loading Flex-based SWFs in AS3-only SWFs

One would think that Adobe would make the integration of Flex-based SWFs and AS3-only SWFs (including those produced by the Flash authoring tool) seamless. Everything works spectacularly if you want to load AS3-only SWFs (or what some folks refer to as Flash SWFs) inside of Flex-based SWFs. But the other way around? Good luck.

In fact, some comments in the Adobe bug database suggest that loading Flex-based SWFs inside of non-Flex-based SWFs is officially unsupported. If so, I'd suggest they change that policy promptly as most of the world still uses non-Flex codebases.

What's the problem? It all revolves around the SystemManager and how it bootstraps Flex-based SWFs. I'll not go into the details, but the problem is that if you don't do things "just so" the SystemManger for your SWF gets confused and doesn't act like the top-level SystemManager that it is.

The fix is easy... down right trivial -- once you know it. It's not in any of the current docs, and Google only leads to a lot of solutions that work if you're not dependent on the SystemManager doing much (such as handling custom cursors or doing complex layout). After lots of stepping through the SystemManager source under different load scenarios I ran across a couple of events (whose names are hard-coded strings in the source) whose purpose was never explained in comments or docs. A bit of Googling revealed this handy doc from the Flex team: Developing and loading sub-applications [PDF]. I couldn't find it's original context nor could I find it navigating the official help docs, so I'm not sure what it's actually a "chapter 1." of (my suspicion is that "chapter 1." is a by-product of their documentation templates).

In the last few pages of that doc they give an example that loads a Flex SWF in a pure AS3 app. Their example is slightly more complicated in that they are trying to load a set of common RPC components to be shared between two potentially different codebases. Our needs are much simpler. Here's all you need to do:

Actionscript:
  1. var loader:Loader = new Loader();
  2. addChild(loader);
  3. loader.load(new URLRequest("my-flex-app.swf"));
  4. loader.addEventListener("mx.managers.SystemManager.isBootstrapRoot", systemManagerHandler);
  5. loader.addEventListener("mx.managers.SystemManager.isStageRoot", systemManagerHandler);
  6.  
  7. function systemManagerHandler(event:Event):void { event.preventDefault(); }

We load the Flex SWF with a Loader, just like normal. We then add two event listeners on our loader (these could probably be on the containing SWF as well as they bubble up). These two events are dispatched by the SystemManager during its initialization phase. If it dispatches these events and nothing happens it makes bad assumptions and gets confused. We need to acknowledge these events. We do this by canceling them (calling event.preventDefault()).

The end result is that your Flex SWF will act completely self-contained. The SystemManager will assume it's the only SystemManager and it will manager everything as if it's the root of the Flex app (which it is).

I've not tested loading multiple Flex SWFs side-by-side with this technique. Due to some singletons (bad, bad singletons!) in the Flex Framework, I'm not sure if this would solve all of your problems as it may not properly coordinate multiple SystemManagers. One solution would be to further wrap the Flex SWF in its own application domain so that there was no cross-pollination of Flex Framework classes.


25
Feb 09

Picnik: Damn Good Flex

I'm pretty sure I've mentioned Picnik before. I got a reminder this evening about updating my expired credit card (for my annual premium). So, I visited the site. I hadn't been in probably six months.

I know they use Flex to build the site (which is what we use for Mockingbird). I haven't read anything about what specifically they do (though, I'm about to go googling that after I post this). Whatever they're doing, it's silky smooth.

For all those folks that say "why do we need Flash on the web?" send them to Picnik. They'll tell you that you can do most of that stuff in JavaScript (not anywhere as smoothly with most web browsers). They'll argue that an app like that should be native -- except that if it was the development cost would have been significantly higher and it would be functionally less useful for users who use multiple machines. The point is that Picnik was made in Flex, by incredibly talented designers and developers, and it was not made in those other environments. There are clearly advantages to the Flash platform that don't exist elsewhere, and those advantages are critical for the right application. Best tool for the job.

I know we made the right decision with Mockingbird (to have it built on Flash). I may be tempted at times by the extra horsepower others may offer -- but in the end, the Flash development environment and distribution platform is a warm, comfortable embrace.


10
Oct 08

Refactoring Mockingbird: Cairngorm

Mockingbird was the first end-to-end Flex application I've built, and definitely the largest application I've built using any technology. Over the last two years, I've learned a whole lot about application architecture.

As I've mentioned previously, we didn't always have time to properly architect everything. Of course, I knew we'd pay a "code tax" on each shortcut. And boy, have we paid them over time. In order to make each new set of changes within a reasonable amount of time my previous hacks require even worse hacks!

In fact, code hacks are exactly the same thing as telling lies. They start small, but the more often you do it the more it snowballs. The hacks (lies) have to get bigger each time in order to incorporate (and compensate for) the earlier hacks (lies).

I've looked at Cairngorm many, many times over the last two years wanting to apply it to Mockingbird. At first, I didn't have a firm enough grasp on Cairngorm to implement it effectively. And once I did, we didn't have the time to refactor Mockingbird to leverage it.

Well, it's time to pay the taxman.

Rapid and consistent development with Cairngorm and Flex. Definitely the clearest explanation of Cairngorm. It's a tidy summary of the entire micro-architecture end-to-end. Also includes an example feature addition and its implementation. This is the sixth (and final) part in a series on Cairngorm that goes into all the pieces in more detail. Definitely recommended reading for the Flex developers out there.


23
Sep 08

Richard Garriott’s Asteroids

Richard Garriott's Asteroids as seen on the Colbert Report. I knocked this up in Mockingbird in about 10 minutes. Very hackish, just downloaded a Garriott picture from Wikipedia and an Asteroids screenshot, cutting out the various pieces. Dead simple.


3
Sep 08

BuiLD YouR WiLD SeLF

BuiLD YouR WiLD SeLF. Great example of avatar creation. Love the art style. I'd love to get a create-a-character this slick inside of Mockingbird.


30
Aug 08

How to get rid of mx:Script blocks in your MXML

UPDATE: Okay, so maybe I just wasn't really paying attention when I've googled this in the past, or maybe I just know more about Flex now and I better understand what's being discussed in the comments... but there's a blog post about code-behind that draws exactly the distinction I outline below and in the comments, some of the Flash/Flex gurus go through the pro's and con's. It's reassuring, at least!

I know, not a very clever title, but I want this post to be easily googled as I would have been elated to get this information 2 years ago when I started using Flex. If anyone know where I could find these details elsewhere, please let me know, as this is exactly the technique I've wanted for a long time.

There's a debate in the Flex community about how AS3 code should be mixed with MXML. There's a code-behind camp that suggests first creating a base class containing your AS3 code (in a .as file) which your MXML code then extends. This has all sorts of drawbacks, like having to declare all instances you want the AS3 code to access in the base class, which kinda defeats the whole purpose of separating the MXML and AS3! Then there's the code-in-front camp that suggests having your AS3 code inherit from your MXML class. Same issues appear, though, just from a different direction.

And finally, there's the more common approach of just using an ugly <mx:Script/> block in your MXML, which is ugly because AS3 code has to first be wrapped in a CDATA block because of the special reserved characters used (angle brackets, double-quotes) and things like "Organize Imports" doesn't work. The end result is a very ugly chunk of non-declarative code in the middle of nice declarative XML.

Of course, some may argue that means you should just probably slice up your code along MVC lines and then you'll have a minimal amount of code in your MXML. Sure, but the reality is that it's never that straightforward, and besides, even with a proper amount of code I'd like to have the automatic import organizing.

And I almost forgot... when you're working in a team situation it's likely that the MXML and the underlying AS3 are developed by different individuals (designer and developer, respectively). Sure, the merging won't be too heinous with the script block, but it would still be required (this is another argument for code-behind/in-front).

So today, entirely by accident, I found the solution. If you include an AS file from exactly one other file (either an AS or MXML file), the Flex Builder editor will treat that included AS file as if it was actually in that file in regards to auto-complete. In other words, if you hit CTRL+SPACE while working in the include file you'll get the context-appropriate auto-complete pop-up as if the file was already included where you used the include directive. If you refer to the included file from two different places in your code, then it won't work (you get whatever is common between the locations).

Oh yeah, and organize imports works perfectly.

So, now in your MXML files you can just do this:

<mx:Script source="my-source-code.as" />

And put all your AS3 code there, neatly tucked away from all your MXML. Nice. All you gotta do is come up with a naming scheme to keep your AS and MXML files grouped (for your convenience only). The MXML compiler actually produces an AS file with the same name as the MXML file, so you can't use that. But you can use a clever variation (that doesn't appear to break anything). The pattern I'm using right now is this:

MyViewComponent.mxml
MyViewComponent.mxml.as

Cool, huh? It almost seems too good to be true. Am I missing something?


7
Jun 08

Functional Programming in AS3

I've never done a lot of functional programming. Sure, I did a bit of required Scheme in my Programming Languages class in college. Didn't do anything real though -- well, maybe a bit of decent AI stuff, but no actual apps. The point is, I've never really worked extensively with functional programming largely because the languages/tools I've been working with haven't been purely functional.

Well, apparently I've just been missing it in AS3. It was staring me plainly in the face but hadn't really thought about it in those terms. Bruce Eckel did, though. He shows how we can use the various Array methods (and add some of our own since Array is dynamic) to work functionally. There's some very clever stuff in there, particularly the in-line functions that leverage closures to return results to the calling method... very clever use of AS3.

There is quite a bit of noise in the article related to his working with Flex Builder and writing the MXML to display the results of his tests. He also has a very -- ahem -- interesting code style that I wouldn't suggest anyone follow if you plan on sharing with other AS3 coders. I'm referring to his mixing of MXML and AS3 (and I'm not talking mx:Script blocks, either). First time I've ever seen that...


4
Jun 08

This is why I use Flash.

Peter Christensen explains why I feel no shame for moving from the world of cutting-edge console games to web-based Flash games.