3 October 2011
Earlier this year, the opportunity arose to launch our puzzle game, Aqualux, on the RIM BlackBerry PlayBook. The BlackBerry Tablet OS has an integrated Adobe AIR runtime, which means you can develop native apps for it using ActionScript 3. As there was already a Flash Player version of Aqualux written in ActionScript 3, the AIR framework made this migration a pretty simple task. Aqualux has been well received on the tablet, earning very high user reviews and being featured on BlackBerry App World at multiple occasions. The game is graphically polished and runs smoothly at 30 fps on the device. This article reviews some of the tweaks and optimizations we made to get Aqualux looking and performing great on mobile hardware.
AIR apps can run in either GPU or CPU mode—the former utilizing the device's dedicated graphics chip to move textures about the screen and the latter rendering everything in the software vector renderer. Unless you're doing a lot of dynamic (i.e, animated) drawing with the Graphics API, GPU mode is the way to go—both for the sake of performance and battery life.
You can activate GPU mode by adding this to your AIR XML Application manifest, under the
<initialWindow> element: <renderMode>gpu</renderMode>
In the end, using the GPU opens up a much higher performance ceiling for your app or game. However, it works a little differently than the software renderer in its handling of display content, filters, and text.
The GPU mode renderer is best suited for rendering bitmaps, not vector art. So your next goal is to convert as much artwork as you can into Bitmap objects, instead of Shapes or Sprites with contents drawn into their Graphics node. Until you've done this, you may find that performance is slower in GPU mode than CPU mode.
If you're using Adobe Flash Professional to import graphics, be sure they are actually being treated as bitmaps in your app. A great deal of the backgrounds and animations for Aqualux were designed and animated inside Flash Professional—a much easier and visual approach than animating and laying everything out with code. However, be aware of the default base class for DisplayObjects created on the Stage in Flash. Even if you import a bitmap graphic into Flash and drag it to the Stage, it will actually be converted to a vector shape and rendered as a fill unless you specify its base class as flash.display.BitmapData in the Library.
If you're drawing graphics at runtime with the vector graphics API, you'll get better performance by converting these to BitmapData objects as well (as long as they aren't animated).
Note: Although you can use the
cacheAsBitmap property as well, I find this to be pretty finicky; it's too easy to apply it to the wrong part of the display list and end up crippling your performance instead of improving it.
To convert Graphics objects to Bitmap, use the
BitmapData.draw function. Yusuke Kawasaki provides a nice class on his blog for doing this, complete with options to control the Antialias level. Note that once your graphics are handled this way, you can set the
Stage.displayQuality property to
LOW, further improving your performance without a noticeable loss in fidelity.
Remember that TextField objects are also vector graphics; so if you're animating them heavily, you may want to draw them to Bitmaps with the
BitmapData.draw() method and dispose of the original. (This applies to tweening of static text only; if your text itself is changing, like a score counter, stick with a plain TextField.)
Filters are rendered in software, and they aren't supported in the GPU rendering mode. Not only will they not show up; your frame rate will drop significantly by having them set, even they though aren't rendered. This doesn't mean you can't have filter effects in your game, though!
Evaluate your content first and look for static objects with filters set on them. The easiest thing to do in this case is to apply the filters offline directly to the image with a graphics program like Adobe Photoshop. For anything else, you can use the
BitmapData.applyFilter() method to apply the filter to your object in a Bitmap. Since applying a filter, such as a glow, will change the dimensions of the image, you'll need to reposition it accordingly. The following code snippet applies the filter
filter to the image
baseData and stores the offset of the new Bitmap in a flash.geom.Rectangle object:
// calculate filters dimensions var filteredRect:Rectangle=baseData.generateFilterRect(baseData.rect,filter); // this will be your offset rect from the original var iRect:Rectangle=baseData.rect.clone(); // offset iRect.x=-filteredRect.x; iRect.y=-filteredRect.y; // the output bitmap var res:FilteredBitmapData=new BitmapData(iRect.width,iRect.height,true); // apply the filter res.applyFilter(baseData,baseData.rect,new Point(-filteredRect.x,-filteredRect.y),filter);
Rethinking your treatment of the Stage display list, and the approach you take to frame-by-frame animation can also yield significant performance gains when running Flash on mobile hardware.
For frame-by-frame animation, avoid using MovieClips—they're slower to redraw. Instead, use a spritesheet—a grid of all the frames in a single bitmap image. There's a number of approaches for this, and the folks at Cheezworld give some excellent samples on their blog.
In Aqualux, the same pipe piece may appear in 20 or 30 places on the screen at once, but it's not a new texture for each one. Instead of creating a new BitmapData instance for repeated textures, use the same BitmapData inside a new Bitmap object. This can give you some significant memory savings, and also avoid the need to re-upload the same texture to the GPU. Gamepoetry has an excellent class (posted on Google Code) for automating this process.
Keep the display list hierarchy as shallow as possible. The fewer child (and grandchild, and further down) objects you have, the better. Use the simplest DisplayObject type that fits your needs as well: Bitmap is better than Sprite is better than MovieClip.
Flash Professional offers a unique value proposition: a strong visual toolset and mature language that can be used to write once and run on the web, and on the desktop, tablet, or phone using Adobe AIR. Resources are certainly more constrained on mobile devices than on the desktop but with a little tweaking, AIR can yield native-speed gaming on these devices. With the release of AIR 3, we can look forward to Stage3D closing the gap even further with real-time 3D via Flash.