Dragging Within Irregular Boundaries Using ActionScript 3

Achieving smooth drag behavior constrained to irregular shapes in AS3 requires two core checks: aligning the dragged item's registration point with the cursor to prevent visual jumps, and detecting collisions with forbiddne zones to revert movement when breached.

Drag Logic Implementation

import flash.events.MouseEvent;
import flash.events.Event;

vehicle.mouseChildren = false;
vehicle.useHandCursor = true;
vehicle.addEventListener(MouseEvent.MOUSE_DOWN, beginDrag);

var offsetX:Number, offsetY:Number;
var isDraggable:Boolean = false;

function beginDrag(e:MouseEvent):void {
    offsetX = vehicle.x - e.stageX;
    offsetY = vehicle.y - e.stageY;
    isDraggable = true;
    stage.addEventListener(MouseEvent.MOUSE_MOVE, dragMove);
    stage.addEventListener(MouseEvent.MOUSE_UP, endDrag);
    addEventListener(Event.ENTER_FRAME, trackCursor);
}

function endDrag(e:MouseEvent):void {
    stage.removeEventListener(MouseEvent.MOUSE_MOVE, dragMove);
    stage.removeEventListener(MouseEvent.MOUSE_UP, endDrag);
    removeEventListener(Event.ENTER_FRAME, trackCursor);
}

function dragMove(e:MouseEvent):void {
    var prevX:Number = vehicle.x;
    var prevY:Number = vehicle.y;
    if (isDraggable) {
        vehicle.x = e.stageX + offsetX;
        vehicle.y = e.stageY + offsetY;
    }
    if (BoundaryCheck.pixelCollision(vehicle, terrain)) {
        vehicle.x = prevX;
        vehicle.y = prevY;
    }
}

function trackCursor(e:Event):void {
    if (vehicle.hitTestPoint(stage.mouseX, stage.mouseY, true)) {
        if (!isDraggable) {
            isDraggable = true;
            offsetX = vehicle.x - stage.mouseX;
            offsetY = vehicle.y - stage.mouseY;
        }
    } else {
        isDraggable = false;
    }
}

Pixel-Precise Collision Detection

Pixel-level collition ensures accurate detection against non-rectangular obstacles by scaling objects in to a grid and analyzing overlapping opaque pixels.

package {
    import flash.display.BitmapData;
    import flash.display.BlendMode;
    import flash.display.DisplayObject;
    import flash.geom.ColorTransform;
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.geom.Rectangle;

    public class BoundaryCheck {
        public static var gridCell:int = 20;

        public static function pixelCollision(objA:DisplayObject, objB:DisplayObject):Boolean {
            var shrinkX:Number = Math.max(1, Math.min(objA.width, objB.width) / gridCell);
            var shrinkY:Number = Math.max(1, Math.min(objA.height, objB.height) / gridCell);
            var origin:Point = new Point();
            var colorXform:ColorTransform = new ColorTransform();
            colorXform.color = 0xFF00000F;

            var overlap:Rectangle = getOverlapRect(objA, objB);
            var scaledOverlap:Rectangle = new Rectangle();
            var result:Rectangle = recursivePixelCheck(objA, objB, shrinkX, shrinkY, origin, colorXform, overlap, scaledOverlap, gridCell, gridCell);
            return result.width > 0;
        }

        private static function getOverlapRect(partA:DisplayObject, partB:DisplayObject):Rectangle {
            if (!partA.root || !partB.root || !partA.hitTestObject(partB)) 
                return new Rectangle();
            var boxA:Rectangle = partA.getBounds(partA.root);
            var boxB:Rectangle = partB.getBounds(partB.root);
            return boxA.intersection(boxB);
        }

        private static function recursivePixelCheck(p1:DisplayObject, p2:DisplayObject, sx:Number, sy:Number, pt:Point, cx:ColorTransform, baseRect:Rectangle, workRect:Rectangle, w:int, h:int):Rectangle {
            if (!p1.hitTestObject(p2)) return new Rectangle();

            workRect.x = baseRect.x;
            workRect.y = baseRect.y;
            workRect.width = baseRect.width / sx;
            workRect.height = baseRect.height / sy;

            var canvas:BitmapData = new BitmapData(w, h, true, 0);
            canvas.draw(p1, buildMatrix(p1, workRect, sx, sy), cx);

            if (sx != 1 || sy != 1)
                canvas.threshold(canvas, canvas.rect, pt, ">", 0, 0xFF00000F);

            canvas.draw(p2, buildMatrix(p2, workRect, sx, sy), cx, BlendMode.ADD);
            var touchCount:int = canvas.threshold(canvas, canvas.rect, pt, ">", 0xFF00000F, 0xFFFF0000);
            var region:Rectangle = canvas.getColorBoundsRect(0xFFFFFFFF, 0xFFFF0000);
            canvas.dispose();

            if (region.width > 0 && (sx > 1 || sy > 1) && touchCount <= (sx + sy) * 1.5) {
                var dx:Number = 0.5;
                var dy:Number = 0.5;
                var nw:int = (region.width != w) ? gridCell : w * 2;
                var nh:int = (region.height != h) ? gridCell : h * 2;

                baseRect.x += (region.x - dx) * sx;
                baseRect.y += (region.y - dy) * sy;
                baseRect.width = (region.width + dx * 2) * sx;
                baseRect.height = (region.height + dy * 2) * sy;

                sx = baseRect.width / nw;
                sy = baseRect.height / nh;
                sx = sx < 2 ? 1 : sx;
                sy = sy < 2 ? 1 : sy;

                region = recursivePixelCheck(p1, p2, sx, sy, pt, cx, baseRect, workRect, nw, nh);
            }
            return region;
        }

        private static function buildMatrix(item:DisplayObject, rect:Rectangle, scaleX:Number, scaleY:Number):Matrix {
            var gbl:Point = item.localToGlobal(new Point());
            var mtx:Matrix = item.transform.concatenatedMatrix.clone();
            var rootMtx:Matrix = item.root.transform.concatenatedMatrix;
            mtx.tx = (gbl.x - rect.x) / scaleX;
            mtx.ty = (gbl.y - rect.y) / scaleY;
            mtx.a /= rootMtx.a * scaleX;
            mtx.d /= rootMtx.d * scaleY;
            return mtx;
        }
    }
}

Tags: ActionScript 3 drag-and-drop irregular boundaries pixel-perfect collision game development

Posted on Sat, 26 Sep 2026 16:30:23 +0000 by scott_ttocs46