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