Search code examples
actionscript-3flashzoominggesturepan

AS3 Constrain Object while Zooming/Panning with GestureEvent / MouseEvent


What I want to do is to keep my MovieClip (which is bigger than the viewport) from having its borders visible. Just like many apps do, e.g. Clash of Clans.

This way if you zoom in and zoom out or pan you will never see the stage under it.

If I was only to pan and not zoom it would be easy because I just had to calculate if my MC rectangle is within my stage and if yes i would just:

mc.x = 0 + mc.width / 2;
//or
mc.x = stage.StageWidth - (mc.width/2);
//or .... The same for y

Now the big problem is when I also zoom! And every time I come up with some code it doesn't work well. There are many examples for zooming via GestureEvent on the web but none of them keeps the MC constrained so you don't see the stage under it.

Can someone please provide an example for me. Lets say the stage is 480x720 and the mc is 1080x720!

You gotta pan and zoom while always covering the stage and the scale of mc will remain within 1-1.5.


Here is my current code:

Multitouch.inputMode = MultitouchInputMode.GESTURE;
stage.addEventListener(TransformGestureEvent.GESTURE_PAN, onPan);
stage.addEventListener(MouseEvent.CLICK, gotoBox);

var isInBox: Boolean = false;
table.x = 203.1;
table.y = 360;
table.scaleX = 1;
table.scaleY = 1;

var mouseTimer: Timer;

function onPan(event: TransformGestureEvent): void
{
    if (!isInBox)
    {
        if (event.phase == GesturePhase.BEGIN)
        {
        stage.removeEventListener(MouseEvent.CLICK, gotoBox);
        trace("noClick");
        }

        table.x += event.offsetX;
        table.y += event.offsetY;
        if (table.x > (table.width / 2))
        {
        table.x = table.width / 2;
        }
        if (table.x < stage.stageWidth - (table.width / 2))
        {
            table.x = stage.stageWidth - (table.width / 2)
        }
        if (table.y > (table.height / 2))
        {
            table.y = table.height / 2;
        }
        if (table.y < stage.stageHeight - (table.height / 2))
        {
            table.y = stage.stageHeight - (table.height / 2)
        }

        if (event.phase == GesturePhase.END)
        {
            if (mouseTimer !== null)
            {
                if (mouseTimer.running)
                {
                    mouseTimer.stop();
                    mouseTimer.removeEventListener(TimerEvent.TIMER, enableClick);
                }
            }
            mouseTimer = new Timer(250);
            mouseTimer.addEventListener(TimerEvent.TIMER, enableClick);
            mouseTimer.start();
            trace("start");
        }
    }
}
function enableClick(e: TimerEvent)
{
    mouseTimer.stop();
    trace("stop");
    mouseTimer.removeEventListener(TimerEvent.TIMER, enableClick);
    stage.addEventListener(MouseEvent.CLICK, gotoBox);
    trace("nowClick");
}
function gotoBox(e: MouseEvent)
{
// here it goes to another frame
}

I cannot add the zooming function because its a total disaster; I used something similar to the function onZoom in this link FlashAndMath

Because I needed to zoom in on a point and out from it, and that is the main issue as I have to move my mc around to make that point in the center WHILE I GOTTA MOVE IT TO MAKE MY WHOLE MC IN A CERTAIN BOUNDARY TO COVER THE STAGE! These too movements act against each other. If this last part is not clear ask me to explain more please:)

After having the right answer from LDMS I updated this question to avoid chat-discussions:)


Solution

  • All you need to do, is whenever you move or scale the object, check to make sure it's not going out of bounds.

    So if you made a function called forceBounds, just call it anytime you scale or change the x/y:

            function forceBounds():void {
                //figure out the bounds to constrain to
                //the x, should be the farthest left it can go without the edge being seen on the right. To get this value just subtract the smaller width (stage) from the larger width (mc) - this should be a negative number
                var bounds:Rectangle = (stage.stageWidth - mc.width, stage.stageHeight - mc.height, mc.width - stage.stageWidth, mc.height - stage.stageHeight);
    
                if (mc.x > bounds.x + bounds.width) {
                    mc.x = bounds.x + bounds.width;
                }
                else if (mc.x < bounds.x) {
                    mc.x= bounds.x;
                }
    
                if (mc.y > bounds.y + bounds.height) {
                    mc.y = bounds.y + bounds.height;
                }
                else if (mc.y < bounds.y) {
                    mc.y = bounds.y;
                }
            }
    

    Here is a full example I made in FlashPro: (with an object on the the stage with an instance name of mc that is bigger than the stage bounds.

    Zoom code taken from FlashAndMath.com

    import flash.ui.Multitouch;
    import flash.ui.MultitouchInputMode;
    import flash.events.TransformGestureEvent;
    import fl.motion.MatrixTransformer;
    import flash.geom.Rectangle;
    import flash.geom.Point;
    import flash.events.GesturePhase;
    
    Multitouch.inputMode = MultitouchInputMode.GESTURE;
    stage.addEventListener(TransformGestureEvent.GESTURE_PAN, onPan);
    stage.addEventListener(TransformGestureEvent.GESTURE_ZOOM, onZoom);
    
    forceBounds();
    
    function onPan(e:TransformGestureEvent):void {
        trace("PAN");
        if(e.phase == GesturePhase.BEGIN){
            stage.removeEventListener(TransformGestureEvent.GESTURE_ZOOM, onZoom);
        }
    
        //localToGlobal is a helper method that converts a point to global coordinates
        var p:Point = DisplayObject(e.target).localToGlobal(new Point(e.offsetX, e.offsetY));
        //conversely, global to local does the opposite
        p = mc.globalToLocal(p);
    
        mc.x = p.x;
        mc.y = p.y;
    
        forceBounds();
    
        if(e.phase == GesturePhase.END){
            stage.addEventListener(TransformGestureEvent.GESTURE_ZOOM, onZoom);
        }
    }
    
    function onZoom(event:TransformGestureEvent):void {
        trace("ZOOM");
        var container:MovieClip = mc;
        var locX:Number=event.localX;
        var locY:Number=event.localY;
        var stX:Number=event.stageX;
        var stY:Number=event.stageY;
        var prevScale:Number=container.scaleX;
        var mat:Matrix;
        var externalPoint=new Point(stX,stY);
        var internalPoint=new Point(locX,locY);
    
        container.scaleX *= (event.scaleX + event.scaleY) * .5;
    
        if(event.scaleX > 1 && container.scaleX > 6){
            container.scaleX=prevScale;
        }
        if(event.scaleY > 1 && container.scaleY > 6){
            container.scaleX=prevScale;
        }
        if(event.scaleX < 1 && container.scaleX < 0.8){
            container.scaleX=prevScale;
        }
        if(event.scaleY < 1 && container.scaleY < 0.8){
            container.scaleX=prevScale;
        }
    
        if(container.scaleX < 1) container.scaleX = 1;
        if(container.scaleX > 1.5) container.scaleX = 1.5;
        container.scaleY = container.scaleX;
    
        mat=container.transform.matrix.clone();
        MatrixTransformer.matchInternalPointWithExternal(mat,internalPoint,externalPoint);
        container.transform.matrix=mat;
    
        forceBounds();
    }
    
    function forceBounds():void {
        //figure out the bounds to constrain to
        //the x, should be the farthest left it can go without the edge being seen on the right. To get this value just subtract the smaller width (stage) from the larger width (mc) - this should be a negative number
        var bounds:Rectangle = new Rectangle(stage.stageWidth - mc.width, stage.stageHeight - mc.height, mc.width - stage.stageWidth, mc.height - stage.stageHeight);
    
        if (mc.x > bounds.x + bounds.width) {
            mc.x = bounds.x + bounds.width;
        }
        else if (mc.x < bounds.x) {
            mc.x= bounds.x;
        }
    
        if (mc.y > bounds.y + bounds.height) {
            mc.y = bounds.y + bounds.height;
        }
        else if (mc.y < bounds.y) {
            mc.y = bounds.y;
        }
    }