Flash and Fullscreen

January 28, 2010

Flash’s fullscreen display implementation is one of the most deceptive aspects that I’ve come across within the Flash platform. On the surface, fullscreen is an unbelievably simple and clean-cut feature: you set the stage’s “displayState” property to either StageDisplayState.FULL_SCREEN or StageDisplayState.NORMAL. However, once you really get digging into fullscreen implementation, you discover all kinds of weird nuances that make this seemingly simple feature a real headache. So, I’ll run through my laundry-list of gotchas and development tactics that I’ve picked up over the years. Here goes…

Toggling fullscreen

Generally I implement a toggle button to click in and out of fullscreen mode. This is super easy to set up, and just uses the basic fullscreen controls of Flash. Nothing fancy going on here; I’d just set this as the CLICK handler of my toggle button…

private function _onToggleFullscreen($event:MouseEvent):void {
 var $fs:Boolean = (stage.displayState == StageDisplayState.FULL_SCREEN);
 stage.displayState = $fs ? StageDisplayState.NORMAL : StageDisplayState.FULL_SCREEN;
 }

Responding to displayState changes

Now, it’s not uncommon to want to rearrange your display a bit when entering/exiting fullscreen mode. I generally shift the alignment, scale, and proportions of elements on screen to better fit the display in fullscreen mode. Now, it would seem like we could just add this reformatting logic into the fullscreen toggle button’s event handler (above) where we actually toggle the displayState. However, this doesn’t work in practice because Flash Player automatically activates the ESC key as an exit from fullscreen mode. This provides a back-door to changing the displayState without going through your toggle button. As a result, all mode-change logic should occur in response to the stage’s RESIZE event (which will trigger in response to your toggle button, and in response to exiting fullscreen with ESC). So, do this:

stage.addEventListener(Event.RESIZE, this._onStageResize, false, 0, true);
function _onStageResize($event:Event):void {
 // perform actions in response to stage resize here...
}

Configuring the fullscreen presentation

The next nuance of this process is considering how you want to present your content in fullscreen mode. By default, your stage will be upscaled to fill the screen along with black space to fill up any differences in aspect ratio. This upscaling is controlled by another stage property called “scaleMode”, which has a set of value constants defined in the StageScaleMode class. By adjusting the stage’s scaleMode property, you can define how stretching is (or is not) applied. But wait, there’s more! Also check out the stage’s “align” property which goes hand-in-hand with the scaleMode property. The “align” property (set to one of the StageAlign class constants) will define how an unscaled stage positions itself while in fullscreen mode.

Now, while the “stage.align” property is a good concept, it has one blatant omission: there is no StageAlign.CENTER option to place the unscaled stage in the center of the screen while in fullscreen mode. This oversight confounds me, and even a good technical explanation from Adobe would not excuse the feature omission in my opinion. So, if you do want to center unscaled content within fullscreen mode, you’ll need to set the stage’s scaleMode to “NO_SCALE”, the stage’s align to “TOP_LEFT”, and then manually do the math to center your root display item within the visible stage boundaries.

Other weird stuff about fullscreen mode

I’m not even sure how to categorize these items, so I’ll just make a list of all the other “gotchas” that I’ve run into…

1) Fullscreen mode does NOT work in a test publish running within the Flash IDE. You’ll need to place the SWF into a browser or publish a projector to have your fullscreen button work.

2) Fullscreen will only work within a web browser if you’ve included a [allowFullScreen="true"] parameter within the Flash embed tag.

3) Flash does not respond to keyboard input while in fullscreen mode (grumble). I’ve looked for the rationale behind this restriction, but have found nothing other than Adobe citing it as a “security measure”. As of Flash Player 10, they threw us a bone and gave us the arrow keys, SPACE, and TAB as fullscreen freebies. Otherwise, you can only get unrestricted fullscreen keyboard access by going through AIR.


SWFAddress revisited

January 26, 2010

It’s been a year since I’ve done anything with SWFAddress, and in that time I’ve received many support questions in response to my last blog post on the subject. So, now that I’m back into SWFAddress on a new project, I’ve finally gotten around to posting a sample implementation of a SWFAddress proxy class in my previous post, available here:

http://lassieadventurestudio.wordpress.com/2008/10/09/better-swfaddress/

I hope it’s useful!


AS3 ‘Stitch’ Particles

December 22, 2009

Happy Holidays! Well, it ’tis the season an the snow is falling, so I thought it might be appropriate to post some relevant ActionScript. So to spread some winter cheer, I’m releasing the AS3 particle library that I wrote for the (numerous) particle effects used in my current game project, “A Stitch in Time“. This particle demo includes a small sampling with snow, fire flies, will-o-wisps, and water bubbles. These core effects have been adapted into many forms in many areas within the game world.

In that demo, all four effects are driven by the same codebase. The particle system is just composed of two base classes: a particle and a particle field. From there, all implementations of the various particle effects just customize the appearance of the particle and the behavior of the field. The source files are available.

So: enjoy, stay warm, and happy holidays! I’ll post again next year after getting back from some holiday travel around South America. Or who knows… maybe I’ll post an update from the road.


“A Stitch in Time” Website

December 2, 2009

In other project-website related news, we’ve recently launched the official “A Stitch in Time” website, available at http://www.lassiegames.com/stitch.

Stitch is the follow-up to Matthias Kempke’s 2007 release, “What Makes You Tick?”. Matt and I partnered up to build this sequel and have been actively developing it within Lassie Shepherd over the past year. While there is no playable demo released to the public yet, the game is making good progress and we still hope to release it in the first half of 2010. In the meantime, the website will act as the definitive source of news about the project.


Lassie website status

November 21, 2009

Howdy folks – as many people have apparently noticed, the Lassie website has gone down at it’s old address. This was partially planned, although mostly just bad planning on my part by not getting the new site location set up before my old web hosting expired. So, I’ll be slowly resurrecting the site on my new server over the coming weeks. While there’s not much up there right now, the site’s new address is: http://lassiegames.com/lassie/

Sorry for the confusion, and thank you all for you patience! I’ve appreciated all the comments and emails which have expressed gratitude toward the Lassie project and hoped it would be back soon. Rest assured–Lassie isn’t going away… there are just too many great game ideas out there to call it quits!

Just for SEO and skimming readers, again, the new website address will be: http://lassiegames.com/lassie/


Lassie Shepherd Released

October 19, 2009

Lassie Shepherd –a complete SCUMM-style adventure game engine built in AS3– is finally released. It’s only taken me two years and a whole lot of lost sleep… The system still isn’t perfect, but then, it will NEVER be perfect; so this is as good a time as any to release it.

The compiled build is posted on the Lassie website; feel free to download and have a go. I encourage developers to turn to the Lassie Forums to set up a peer-to-peer support community. Unfortunately, I have no availability to commit to technical support. All I can say is —as always— good luck and happy adventuring!


Easy Blowfish Text Encryption

September 2, 2009

All security considerations aside, text encryption is great just for keeping curious editors out of a plain text file. Case of point: I build XML driven Flash games. While I don’t necessarily care if a player “looks under the hood” at the XML data, I would rather that they not try to cheat gameplay by manually tweaking XML configuration. So, a quick encryption of the XML works great to impede manual edits.

So, I’ve always turned to Blowfish for a quick encryption. Blowfish is a two-way cipher that can both encrypt and decrypt text. And thankfully, the fine folks at the AS3 Crypto project on Google code have included an AS3 implementation of the algorithm as part of their open source library. However, the AS3 Crypto library is pretty extensive; and likewise, it takes some extensive research to figure out all the parts and pieces. In my case, I wasn’t so worried about security as I was about having something easy that would generate an encryption. So, I put together this utility class which exposes a simple interface to the Blowfish algorithm and only imports the necessary library items (this import amounts to 11Kb, rather than the 40Kb of the full AS3 Crypto library). You’ll need to download the AS3 Crypto library to go with this utility class.

package
{
 import com.hurlant.util.Base64;
 import com.hurlant.crypto.symmetric.PKCS5;
 import com.hurlant.crypto.symmetric.ECBMode;
 import com.hurlant.crypto.symmetric.BlowFishKey;
 import com.hurlant.crypto.symmetric.ICipher;
 import com.hurlant.crypto.symmetric.IPad;
 import flash.utils.ByteArray;

 public class Blowfish
 {
 /**
 * Encrypts a string.
 * @param text  The text string to encrypt.
 * @param key  A cipher key to encrypt the text with.
 */
 static public function encrypt($text:String, $key:String=""):String
 {
 try
 {
 var $output:ByteArray = new ByteArray();
 $output.writeUTF($text);
 var $pad:IPad = new PKCS5();
 var $cipher:ICipher = _getCipher( $key, $pad );
 $pad.setBlockSize( $cipher.getBlockSize() );
 $cipher.encrypt( $output );
 $cipher.dispose();
 return Base64.encodeByteArray( $output );
 }
 catch ($error:Error)
 {
 trace("An encryption error occured.");
 }
 return null;
 }

 /**
 * Decrypts an encrypted string.
 * @param text  The text string to decrypt.
 * @param key  The key used while originally encrypting the text.
 */
 static public function decrypt($text:String, $key:String=""):String
 {
 try
 {
 var $input:ByteArray = Base64.decodeToByteArray( $text );
 var $pad:IPad = new PKCS5();
 var $cipher:ICipher = _getCipher( $key, $pad );
 $pad.setBlockSize( $cipher.getBlockSize() );
 $cipher.decrypt( $input );
 $cipher.dispose();
 $input.position = 0;
 return $input.readUTF();
 }
 catch ($error:Error)
 {
 trace("A decryption error occured.");
 }
 return null;
 }

 /** @private builds a Blowfish cipher algorithm. */
 private static function _getCipher( $key:String, $pad:IPad ):ICipher {
 return new ECBMode( new BlowFishKey(Base64.decodeToByteArray( $key )), $pad );
 }
 }
}

Implementation of this Blowfish utility class is extremely easy. Just import the class, then call its static encrypt() and decrypt() methods. The first param of each method is the text to encrypt, and the second params are the encryption key. When decrypting text, you must provide the same key that the text was originally encrypted with. Here’s a sample implementation:

import Blowfish;
var $key:String = "lockme";
var $encryption:String = Blowfish.encrypt("Don't read this text!", $key);

trace( $encryption );
// return: "sm63oSsAgk0T8TWad0vjRMv5KV7COH80"

trace( Blowfish.decrypt($encryption, $key) );
// return: "Don't read this text!"

Flash Accessibility

August 28, 2009

I’ve been doing some work for the National Park Service recently, and it’s gotten me into Flash accessibility. Accessibility is a big deal for government clients because all “.gov” websites mandate 508 compliance – which requires that all content be visible to screen readers, like JAWS.

While it’s pretty easy to get Flash content exposed to a screen reader, it’s fairly tedious to get the presentation flowing nicely with both with visual controls and accessibility controls. It doesn’t help that Flash’s accessibility documentation is fairly slim and there aren’t a lot of developers discussing it. So, after a few days worth of development and testing on the subject, I’ll share my findings.

1) Make DisplayObjects accessible.

Start by giving each button an “AccessibilityProperties” object. The AccessibilityProperties object can have a name and description assigned to it. From what I can tell, the “name” field is the key piece of content given that it’s automatically read when the button gets focus. I never found a way to access the “description” field through my screen reader (but then I’m not a JAWS power user either). Also, enable “forceSimple” on the AccessibilityProperties object so that none of the object’s children are read. Otherwise, the screen reader will read a button’s text content.

2) Restrict tab ranges

Flash will, by default, step through all buttons on stage. However, chances are that you won’t want every single control included within the accessible presentation. So, use “tabEnabled” and “tabIndex” (properties of InteractiveObject) to control which buttons are allowed to receive tab focus. Set “tabEnabled” to false on all buttons that are not part of the accessible presentation, or just set “tabChildren” (property of Sprite) to false on a high-level display container to completely disable tabbing within that display branch. Finally, manually assign a numeric “tabIndex” value for each accessible button to guarantee the order in which the buttons will be tabbed.

After setting accessibility properties and limiting tab order, you should be well on your way to having a decent accessibility presentation.

3) Synchronizing accessible and visual presentations.

Tabbing is great for an accessibility presentation, although it has the potential to get out of synch with the visual presentation (which is probably controlled by mouse clicks). So, it’s important to have both aspects of the presentation work together. This means that tabs need to simulate mouse clicks, and vice versa.

Simulating a mouse click to a button upon tabbing to it is easy. Just have the button listen for FocusEvent.FOCUS_IN, and have that trigger the button’s click action. Works like a charm.

Making a button obtain tab focus upon click is also  simple, given that Flash automatically gives focus to an object when its clicked. In the event that you need to manually give a button focus, use the “stage.focus” display property.

4) Forcing a screen reader to read an item.

In practice, the screen reader will read any new content that is set to the “stage.focus” property. So if you have a single object with changing content, you can trick a re-reading of the object by changing its accessibility properties, calling Accessibility.updateProperties(), and then nullify and reset the “stage.focus” property.

5) Buttons off stage don’t get read.

This was driving me nuts: I made a scrolling row of thumbnail images that you can flip through using the keyboard (tab/arrows). While flipping through thumbnails, the active thumbnail would slide into view if it was accessed outside of the scroll area. The solution worked great, visually. However, while Flash could tab to a button off of stage, JAWS wouldn’t read it. Once I deduced that stage bounds were the issue, I solved the problem by creating a second set of 1×1 pixel buttons hidden on stage that proxied the main buttons’ tab behavior. It seemed clunky, but it worked. However, it revealed another advantage: Flash has native support for intelligent arrow key controls based on tabEnabled buttons’ proximity to one another. When you line up a bunch of tabable button in a horizontal row, the left/right arrow keys are automatically set up to flip through them. Same goes for a vertical column: up/down arrow will be enabled for you. So, this tab proxy trick allows the tabEnabled buttons to be arranged in a horizontal or vertical fashion, regardless of the layout of the main visual buttons.

So, hopefully someone else will find this helpful. If any readers have other tips or tricks, please feel free to post them in comments!


Flint, Particles, and Animation Rendering

June 29, 2009

Sorry all for my abysmal rate of posts recently. To say the least, I’ve been busy with “A Stitch in Time” rolling into full development. However, that’s not to say that I don’t have material to post on – just lack of time to write about it.

However, I’ll take a sec here to cover one new area I’ve been getting heavily into: particle animation. For bigger, more complex animations like fire, smoke, and falling water, there is nothing quicker, easier, and better looking than a simple particle system. So, I’ve become a huge advocate of the Flint particle system. For those unfamiliar, Flint is an open-source particle engine built in AS3. It is super object-oriented, and may look a little intimidating at first if you’re not entirely comfortable with importing and instancing classes. However, you get the hang of it as you use it.

Flint makes some great animations. I set up a waterfall with a series of 6 particle emitters flowing down over an illustrated rock face, and it really looked beautiful. However, they also amounted to about 1200 particles – which proved to be a massive CPU hog. Ugh. Beautiful animation, but completely useless to my game world given that they’d cut the environment’s frame rate in half. So, I got thinking… how could I get the same animation without a performance hit?

The answer came from one of my own past blog posts about writing images out of flash. The post covers a script I had written to use the Adobe corelib’s PNG encoder to capture a display object as bitmap data and send to to PHP to be written. I was curious… would that work for “filming” a PNG sequence of a particle animation? 15 minutes later, I found the answer is – yes, it would.

The tool I built was simple: just snap a BitmapData image of a MovieClip on stage for X sequential frames, storing each frame in an array. Once X frames have been captured, encode each item in the array as a PNG and send it off to PHP to be written as a file. Now, that’s a pretty heavy burden on a network connection when you’re sending a sequence of 30+ PNG images off using URLLoaders. However, I tried the process running PHP locally on my Mac using MAMP, and it worked perfectly – with 1 second of 30FPS image capturing and less than a second of file writing, I had a folder full of pre-rendered particle animation frames. I did some post-production to make them loop, dropped the frames into a MovieClip, and stuck them into the game world. The waterfall looks fantastic, and there was no loss of performance.

The only downside to this solution is –obviously– filesize. Flint gave me a beautifully random animation for 16Kb. Pre-rendering gave me a beautiful 15-frame loop for 250Kb. This is certianly a compromise, and one that I would be very mindful of for larger animations intended for the web. However, in my circumstance I’m more focused on performance than filesize, so this worked for me. Hopefully, this may give other folks some ideas on routes to explore while optimizing animations.


Zoom and Pan Image Display

April 8, 2009

I’ve done enough zooming image displays recently that I formalized my code base into a class abstract. I thought I’d post it in case it’s useful to others.

The broad overview is that ZoomImage is a display container that acts as a mask for a child image. By calling the “zoomIn()” and “zoomOut()” methods,  the child image will zoom within the frame created by ZoomImage’s scrollRect. Once the image is zoomed in on, you can click and drag the zoomed image to pan it within the frame. ZoomImage takes care of all the math for zooming the image in and out of the center point of origin, and for restraining the image’s position to within the bounds of the frame.

This class does have one dependency on another code library: that being the awesome TweenLite AS3 library by GreenStock. If you’ve never used TweenLite before, then check it out. It’s extremely easy to use and is unbelievably handy! Just download and unpack TweenLite into your class library where ZoomImage can reference it.

Cheers!

ZoomImage.as

package
{
    import flash.display.Sprite;
    import flash.geom.Rectangle;
    import flash.events.MouseEvent;
    import gs.TweenLite;
    import gs.easing.*;

    /**
    * ZoomImage is a container for a zooming, panning image.
    * NOTE : this class has a dependency on the TweenLite AS3 library, available at:
    * http://blog.greensock.com/tweenliteas3/
    */
    public class ZoomImage extends Sprite
    {
        /** Minimum allowed scale value for the image. */
        public var minScale:Number = 1;

        /** Maximum allowed scale value for the image. */
        public var maxScale:Number = 4;

        /** The ammount that the image's scale is modified with each zoom in/out call.  */
        public var scaleIncrement:Number = 0.5;

        /** @private the cropped, zooming image. This MUST be a Sprite so that start/stop drag methods can be utilized. */
        private var _image:Sprite;

        public function ZoomImage($width:int=400, $height:int=300):void
        {
            super();
            scrollRect = new Rectangle(0, 0, $width, $height);
            addEventListener(MouseEvent.MOUSE_DOWN, this._onMousePress, false, 0, true);
            _image = _setImage();
        }

        /**
        * Creates the image asset (this can be customized as needed).
        * In this case, the demo image is being drawn as a vector.
        */
        private function _setImage():Sprite
        {
            // create an image display object.
            var $image:Sprite = new Sprite();

            // draw rectangle
            $image.graphics.beginFill(0xCCCCCC, 1);
            $image.graphics.drawRect(0, 0, displayWidth, displayHeight);
            $image.graphics.endFill();

            // draw circle
            $image.graphics.beginFill(0x999999, 1);
            $image.graphics.drawCircle(displayWidth/2, displayHeight/2, 30);
            $image.graphics.endFill();

            // draw X
            $image.graphics.lineStyle(1, 0x000000, 1);
            $image.graphics.moveTo(0, 0);
            $image.graphics.lineTo(displayWidth, displayHeight);
            $image.graphics.moveTo(0, displayHeight);
            $image.graphics.lineTo(displayWidth, 0);

            // add and return new image child.
            addChild($image);
            return $image;
        }

        /**
        * Specifies the width of the visible image area.
        */
        public function get displayWidth():int {
            return scrollRect.width;
        }

        /**
        * Specifies the height of the visible image area.
        */
        public function get displayHeight():int {
            return scrollRect.height;
        }

        /**
        * Zooms out from the image.
        */
        public function zoomIn():void {
            _zoom(1);
        }

        /**
        * Zooms in on the image.
        */
        public function zoomOut():void {
            _zoom(-1);
        }

        /**
        * @private
        * Zooms the image display.
        * @param $dir Direction of zoom, as expressed by a value of "1" (in) or "-1" (out)
        */
        private function _zoom($dir:Number):void
        {
            var $scale:Number = Math.max(minScale, Math.min(_image.scaleX + (scaleIncrement * $dir), maxScale));
            var $postW:int = (_image.width / _image.scaleX) * $scale;
            var $postH:int = (_image.height / _image.scaleY) * $scale;
            var $x:int = Math.min(0, Math.max(_image.x+((_image.width - $postW)/2), displayWidth-$postW));
            var $y:int = Math.min(0, Math.max(_image.y+((_image.height - $postH)/2), displayHeight-$postH));

            TweenLite.killTweensOf(_image);
            TweenLite.to(_image, 1, {scaleX:$scale, scaleY:$scale, x:$x, y:$y, ease:Strong.easeInOut});
        }

        /** @private called upon any mouseDown event on the image */
        function _onMousePress($event:MouseEvent):void {
            stage.addEventListener(MouseEvent.MOUSE_UP, this._onMouseRelease, false, 0, true);
            var $dx:int = _image.width - displayWidth;
            var $dy:int = _image.height - displayHeight;
            _image.startDrag(false, new Rectangle(-$dx, -$dy, $dx, $dy));
            cacheAsBitmap = true;
        }

        /** @private called upon any mouseUp event following a press on the image */
        function _onMouseRelease($event:MouseEvent):void {
            stage.removeEventListener(MouseEvent.MOUSE_UP, this._onMouseRelease);
            _image.stopDrag();
            cacheAsBitmap = false;
        }
    }
}