Programming


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.


24
Oct 08

Minimize Code, Maximize Data

The Database Programmer: Minimize Code, Maximize Data: an excellent description of something I've learned as I've worked with game development tools over the last 10 years (warning: quickly becomes SQL-focused). It always distinguishes game makers who come from an art/design background and game makers who come from an engineering/scripting background.

Non-programmers only have data to work with so they solve their design problems by pushing different/more data into a fixed toolset. Programmers solve design problems by writing software. Unfortunately, the way software ecosystems work, the data-side of the equation is less error-prone and is the fastest to iterate on.

One thing we've tried to do with Mockingbird is build a game development toolset for non-programmers. Not folks who don't program (yet), but non-programmers -- folks who won't ever be (by choice) programmers. HTML does the same thing (which Mockingbird is closely modeled after). That's one of the big reasons "it works."


13
Oct 08

Refactoring Mockingbird: Hypermedia as the Engine of Application State

I've seen this discussed several times in my REST research (including the original REST paper), but I found this to be the most straightforward explanation of why it's advantageous:

Peter Williams - Hypermedia as the Engine of Application State.

Mockingbird's REST API is pretty straightforward because documents are constructed on the client and the server API essentially just loads and saves whole documents. But it's intriguing that it could be completely encapsulated on the server by simply doing what the server already does for humans...


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.


9
Oct 08

Refactoring Mockingbird: Singletons

I'm starting the process of refactoring the core and components of Mockingbird. Now that we've settled on a featureset and UI, it's time to go back and clean up all those unused abstractions, unnecessary architecture and *hacks* that litter the code (the byproduct of a single engineer writing all of it!).

First up: singletons (or any global state). I'm a big OOP fan. I'm a big pattern fan. I'm a big GOF fan. I don't like singletons. Or rather, I don't like programmers pretending that singletons are "good OOP" and that they're somehow different than old fashioned globals. I've always reminded other developers, "if you're using singletons as globals, that's fine, it's the proper way to do it in an OOP language. But don't kid yourself! It's a global with different syntax! It's got all of the same problems!"

Singleton I love you, but you're bringing me down | // Coding Without Comments.

This article really sums up everything much better than I could and is what inspired me to write this post. Mockingbird's singletons have nagged me for a long time, and I resisted having them for a long time before then. I was basically using Dependency Injection, which worked, but felt like more typing and repetition than necessary for the *potential* of configurability down the road. I eventually caved and refactored those parts of the architecture around singletons. It simplified the code quite a bit (in the short term), and made prototyping much faster as I now had a nice global dumping ground for quick hacks.

But, it's definitely given the code an awful smell. And it's made it very brittle. Up until I saw the above post, my plan was to just convert everything back to Dependency Injection and just live with the extra boilerplate. But, there's a better route! Factories!

So, in the above post there's a link to the blog of the testing expert over at Google. His suggestion is to never mix object construction with logic. Simple, yes? Instead of manually handling dependency injection in each place where you need to construct an object (which has a cascading effect wherein you need more and more dependency injection!) you consolidate all of that boilerplate code to a factory. A clear example of separation of concerns.

Now, I used to be in love with factories. When I developed in C/C++ I used factories all of the time because it allowed me to consolidate memory management into the factory and strip all the mallocs out of my logic. But I cast that lesson aside when I moved to managed languages. But factories still solve the same fundamental issue of allocation as a *part* of configuration (which is what factories generically do). Obvious, yes, but I'd never seen it put quite like this.

Needless to say, it's clicked for me. I'm now going to be introducing factories in lots of places inside of Mockingbird. Creating elements of our Game Object Model? Factory! Creating runtime instances? Factory!

To be honest, I was actually employing a bit of the factory pattern in most of the instances already. The problem was that I was not being formal enough about it, or rather I wasn't *completely and cleanly* separating my concerns.


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.