Archive for April, 2009|Monthly archive page

Zoom and Pan Image Display

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;
        }
    }
}
Advertisements

A Stitch In Time

Today I’m finally kicking off the PR campaign for my project of the year: A Stitch in Time, the long awaited sequel to Matthias Kempke’s What Makes You Tick?. This title has been a long time in the coming; and still has a long, difficult road of heavy development ahead. For those interested in the backstory thus far, I’ll catch you up on the details…

What Makes You Tick? launched in May of 2007. It was a pretty exciting release for me, given that it was the first complete end-to-end game built with the Lassie engine. Plus, Tick? was built by none other than my dear friend Matthias Kempke who I had met back in 2004 while touring Germany. During that visit I gave Matt the first-ever demo release of the Lassie engine. Matt was one of Lassie’s first independent developers, although he quietly built What Makes You Tick? behind closed doors on his side of the Atlantic… I never saw so much as a peep at his progress until the game was finished. That said, I had no idea that a Monkey Island whistling depiction of myself would make a cameo in Tick?, and –quite frankly– I nearly fell out of my chair when I played through and met “myself” for the first time… geeze, he even drew me wearing my complete backpacking attire from the 2004 trip. Spooky.

Now, while I didn’t get much of a sneak-peak at What Makes You Tick? before it was publicly released, I did at least get to see the original ending. If you’ve played the game, you probably know what happens: Whaamo! Fade to black… And originally, the game concluded with “To be continued”. I strongly urged Matt to remove the “To be continued” statement, given that it promised too much and almost undermined the weight of the cliff-hanger which was strangely satisfying in its own twisted way. He obliged, and I think we’ve delighted as many fans as we’ve “ticked off” with that decision.

Anywho, Matt and I met up again in the fall of 2007 to go hiking through the Swiss Alps. What Makes You Tick? had gained enough popularity in its six months since release to be called a success, and Matt was considering options for a new game concept using strong themes from Kafka literature. We batted some ideas around and ended up skidding sideways into a What Makes You Tick? connection that we both agreed really worked. After a few pints in an Interlaken pub one night, it was safe to say that the Kafka idea had taken the shape of a What Makes You Tick? sequel.

We chatted about this concept for over a year – proposing characters, places, and story elements; all in a pretty casual forum style that would need a lot more commitment before it went anywhere. While we generated a lot of ideas in 2008, we didn’t generate a whole lot of material. However, we were planning to meet up again in late 2008 so put Tick2 on the agenda of things to discuss.

We met up in September 2008 in Prague, Czech Republic which is –coincidentally and appropriately– the birthplace of Franz Kafka. The Prague summit faced the tough reality of where we are in our lives and that if we were ever going to do this, it had to be now. In some respects, it was an easy choice: the story had to be told. In others though, it’s difficult given that we do need to deviate from the roots of our hobbyist innocence. Big productions don’t happen without designated human resources, and human resources need to eat. Somewhere along the line the money factor enters the equation, which means moving away from indie gaming and into commercial production. And that’s a tough transition. No one wants to sell out; but you can’t produce real content without designated people who need to make a (meager) living. And the money prospect is particularly scary when you face the potential of bad sales turning into a lost investment. I guess we’ve managed to rationalize that a bit though: if our game sells, then we look forward to making additional titles in the future. If it flops… well, at least we got it out of our system in a BIG way.

Either way, we’re having fun producing the title. If players can have half as much fun playing it as we have designing it, then we’ve been (at least marginally) successful. Either way, we think that fans of What Makes You Tick? will find some interesting and exciting depth in A Stitch in Time‘s story, even if we decide not to reveal what happens at the end of the first game… IF. ;-)