Preventing the bounding box of transparent Bitmap Sprite from triggering mouse events - actionscript-3

I thought I had solved my mouseEvent problem for Sprites containing a Bitmap with an alpha channel but I've encountered a new issue shown in the image below: the bounding box of the "Eurasia" map Sprite is triggering a `MouseEvent.Roll_Out" for the "Africa" Sprite.
My setup: Each map piece is a Sprite with a child Bitmap (PNG with alpha) and a "hitArea" Sprite derived from the Bitmap. The relevant code is below. This works great – except in the case where there are bounding box overlaps. The eventListeners I attach to each Sprite use MouseEvent.ROLL_OVER and MouseEvent.ROLL_OUT but I have also tried MouseEvent.MOUSE_OVER and MouseEvent.MOUSE_OUT.
I've tried attaching eventlisteners to the "hitArea" Sprite and various other things but I can't get the bounding box to be ignored. Are there any settings I may have missed – or a workaround?
Code:
buttonImage = new Bitmap(upImageData);
buttonImage.smoothing = true;
this.addChild(buttonImage);
hitSprite = createHitArea(upImageData, 4);
this.addChild(hitSprite);
hitSprite.visible = false;
hitSprite.mouseEnabled = false;
this.hitArea = hitSprite;
public function createHitArea(bitmapData:BitmapData, grainSize:uint=1):Sprite
{
var _hitarea:Sprite = new Sprite();
_hitarea.graphics.beginFill(0x000000, 1.0);
for(var x:uint=0;x<bitmapData.width;x+=grainSize) {
for(var y:uint=grainSize;y<bitmapData.height;y+=grainSize) {
if(x<=bitmapData.width && y<=bitmapData.height && bitmapData.getPixel(x,y)!=0) {
_hitarea.graphics.drawRect(x,y,grainSize,grainSize);
}
}
}
_hitarea.graphics.endFill();
_hitarea.cacheAsBitmap = true;
return _hitarea;
}

If using a vector mask is not a viable option (it should work if you changed your hitSpite into a Shape and then made it the mask of the map piece sprite - also you'd have to add it as a sibling of the map piece and not a child), then the way most people do this is checking if the pixel under the mouse is transparent or not.
Here is an example:
Let's say all your map pieces are the sole children of a Sprite referenced in a var called container. Let's also make the assumption for the example, that your map pieces are all Sprites that have the png Bitmap as the bottom most child.
You need to add the click listener to the container (instead of each individual map piece):
container.addEventListener(MouseEvent.CLICK,click);
function click(e:MouseEvent):void {
var child:Sprite; //a helper var to store the current iterations child below in the while loop
var target:Sprite; //the item that we determined was clicked
//iterate through all children of container (backwards, so we process the top most layer first)
var i:int = container.numChildren;
while(i--){
child = container.getChildAt(i) as Sprite; //populate the child var
//now we check if the mouse is over this child by a simple hit test point
//we also then check if the pixel under the mouse is transparent (a value of 0)
if(child.hitTestPoint(e.stageX, e.stageY) && Bitmap(child.getChildAt(0)).bitmapData.getPixel32(child.x + e.localX,child.y + e.localY)){
target = child;
break; //break out of the loop since we found a child that meets the criteria
}
}
trace(target);
//now do something with target
}

Related

Stretch and rotate a Movieclip without distortion

i'm building a flash desktop app, where the user needs to link two Movieclips on stage (a computer and a router) using a line (or whatever can do the job), i want to achieve this same exact effect: image1. I searched and found this solution, i tried the code and did some modifications:
link.addEventListener(MouseEvent.CLICK, linkOnClick);
function linkOnClick(e:MouseEvent){
this.addEventListener(Event.ENTER_FRAME, enterFrame);
var linkPoint:Point = new Point(link.x, link.y);
var mousePoint:Point = new Point();
var distance:Number;
var radians:Number;
function enterFrame(e:Event):void {
//Distance
mousePoint.x = stage.mouseX;
mousePoint.y = stage.mouseY;
distance = Point.distance(linkPoint, mousePoint);
link.width = distance;
//Rotation
radians = Math.atan2(stage.mouseY - link.y, stage.mouseX - link.x);
link.rotation = radians * (180/ Math.PI);
if(link.hitTestObject(router)){trace("Success");}
}
When i compiled the code i got this: image2, so as you may remark, the problems i found are:
1-the edge of the line follows the direction of the mouse, but sometimes it goes beyond the cursor, i want the cursor to drag the edge of the line.
2-the line changes it's width, if it's 90° degrees the line width is so remarkable, i want the line to have a constant width.
how can i acheive the same exact effect shown in image1 ?
// First, lets create mouse-transparent container for drawing.
var DrawingLayer:Shape = new Shape;
addChild(DrawingLayer);
// Hook the event for starting.
stage.addEventListener(MouseEvent.MOUSE_DOWN, onDown);
// Define a storage for keeping the initial coordinates.
var mouseOrigin:Point = new Point;
function onDown(e:MouseEvent):void
{
// Save the initial coordinates.
mouseOrigin.x = DrawingLayer.mouseX;
mouseOrigin.y = DrawingLayer.mouseY;
// Hook the events for drawing and finishing.
stage.addEventListener(MouseEvent.MOUSE_UP, onUp);
stage.addEventListener(MouseEvent.MOUSE_MOVE, onDraw);
}
function onDraw(e:MouseEvent):void
{
// Remove the previous line.
DrawingLayer.graphics.clear();
// Draw a new line.
DrawingLayer.graphics.lineStyle(5, 0xFF6600);
DrawingLayer.graphics.moveTo(mouseOrigin.x, mouseOrigin.y);
DrawingLayer.graphics.lineTo(DrawingLayer.mouseX, DrawingLayer.mouseY);
}
function onUp(e:MouseEvent):void
{
// Unhook the events for drawing and finishing.
stage.removeEventListener(MouseEvent.MOUSE_UP, onUp);
stage.removeEventListener(MouseEvent.MOUSE_MOVE, onDraw);
}
It's because of that the actionscript is trying to stretch the line thickness by changing its container MovieClip's scale. But you can prevent this by setting the line Scale option to None.
To do that, select your line and open the properties menu and then select None from the drop down menu of the Scale option.
But,
I recommend you to draw a line by a code: Draw line from object to Mouse (AS3)
Write below code:
this.graphic.clear ();
this.graphic.lineStyle(0x000000);
this.moveTo(startPoint.x,startPoint.y);
this.lineTo(endpoint.X,endpoint.y);

Flash AS3: Typewriter effect with copyPixels

I'm making a Flash AS3 based game, and I'm building a custom font system. It works like this: a BitmapData class takes a PNG file from the library (FontSource), loops between every character in a given string and then gets its x, y, width and height inside the image from a function (getOffset), and then it uses copyPixels to draw the custom-font text.
Here's the code:
public function draw(str) {
var png = new FontSource(480, 32);
var offsets;
var char;
var rect;
var x;
var y;
this.lock();
for (var i = 0; i < str.length; i++) {
char = str.substr(i, 1);
offsets = getOffsets(char);
rect = new Rectangle(offsets[0], offsets[1], offsets[2], offsets[3]);
this.copyPixels(png, rect, new Point(x, y));
x += offsets[2];
}
this.unlock();
}
Now, the question here is: I'm building a typewriter effect class based on the ENTER_FRAME event; each new frame, a new character is added to a string. So, I wanted to ask which one of these approaches would do better related in a matter of performance:
1) Call draw() to redraw the whole BitmapData each frame.
2) Making an alpha mask and expand its width each frame.
3) Making separate Objects and adding them to stage each frame.
Thank you for your time.
As an alternative, you don't need to redraw the entire string. You can just draw more characters at its end. I'd implement this as follows: You give your bitmapped textfield a string it should draw per frame, once. It clears, then at each enter frame event it just adds 1 to the length of the string drawn, and draws only one single character. Just save more data in your class for this. For example:
public class BTF extends Sprite {
private var str:String;
private var source:FontSource; // set once!
private var bd:BitmapData; // the one that'll get drawn
private var lastIndex:int;
private var lastX:int; // coords to draw next symbol to
// assuming Y is always 0 - adjust per font
public function BTF(source:FontSource,width:int,height:int) {
this.source=source;
bd=new BitmapData(width,height,0x00808080,true); // check if I didn't mix up parameters
addChild(new Bitmap(bd)); // make it be drawn on your BTF
}
... // rest of class skipped
public function onEnterFrame(e:Event):void {
if (lastIndex>=str.length) return; // drawn all text already
var c:char=str.charAt(lastIndex++); // get next char to draw
var r:Rectangle=source.getOffsets(c); // you need to specify source for that - it's font that gives you bounds
bd.copyPixels(source.fontBitmapData,r,new Point(lastX,0)); // draw that char
lastX+=r.width; // move "cursor"
}

Flash/AS3 - MovieClip into Bitmap

I need to turn a MovieClip that I have on the stage into a Bitmap. The function I have made for this halfway works; it does make a Bitmap from the MovieClip with the correct image, but it does not rotate it.
Here is the function
function makeBitmapData(mov):BitmapData
{
var bmpData:BitmapData = new BitmapData(mov.width, mov.height, true, 0);
bmpData.draw(mov);
this.addChild(new Bitmap(bmpData)); //Shows the bitmap on screen purely for example
return bmpData;
}
Here is the output
How should I rotate the bitmap or just purely copy all the pixels in that bitmap, rotated and all?
Have you checked rotate() function and fl.motion.MatrixTransformer class? Also this question looks helpful.
In the function that implements your code.
var b:Bitmap = new Bitmap ( makeBitmapData(mov) );
addChild(b);
b.rotation = mov.rotation;
One way to accomplish this, is to draw from the items parent instead, that way any transformation/filters will be reflected in the bitmap data captured. So something like this:
function makeBitmapData(mov:DisplayObject):BitmapData
{
var rect:Rectangle = mov.getBounds(mov.parent); //get the bounds of the item relative to it's parent
var bmpData:BitmapData = new BitmapData(rect.width, rect.height, false, 0xFF0000);
//you have to pass a matrix so you only draw the part of the parent that contains the child.
bmpData.draw(mov.parent, new Matrix(1,0,0,1,-rect.x,-rect.y));
addChild(new Bitmap(bmpData)); //Shows the bitmap on screen purely for example
return bmpData;
}

Actionscript Image with clickable spots

Can any one help in suggesting a solution for the following:
i have a large image, consider it as a map, i want to put this image in a viewer that is smaller than the image and i have to be able to scroll the image by clicking and dragging it.
and i want to put in this image a clickable spots in a specified x and y coordinated, and be able to click the spots.
when clicking any spot in the image, the image will be changed with a new spots.. and so on..
can you help in suggesting what is the best object to load the image in and be able to do all the mentioned points.
Thanks in advance.
This is easier than you think. You have a few goals to consider:
"i want to put this image in a viewer that is smaller than the image": You dont need anything special to do this. The concept of this is simply that you have a mask overlay where you want the large image visible.
var viewer:Sprite = new Sprite; //200x200
var imageMask:Sprite = new Sprite; //200x200
var imageContainer:Sprite = new Sprite; //400x500
imageContainer.mask = imageMask;
viewer.addChild(imageContainer);
//this will allow you to visibly see only 200x200 of the
//imageContainer at any time
"i have to be able to scroll the image by clicking and dragging it": This is a little more logic as the imageContainer will have to move in the -(negative) direction of the mouse. Add some listeners to check for mouse actions, and drag as required.
var allowDrag:Boolean = false;
imageContainer.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
imageContainer.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
imageContainer.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
function onMouseDown(e:Event):void{
allowDrag = true;
}
function onMouseUp(e:Event):void{
allowDrag = false;
}
function onMouseMove(e:Event):void{
//return if not dragging
if(!allowDrag) return;
//move the imageContainer in a -(negative) direction of the mouse
//use an index relative to the size of the viewer and imageContainer
var speed:Number = 0.5;
imageContainer.x -= (viewer.width/imageContainer.width)*speed;
imageContainer.y -= (viewer.height/imageContainer.height)*speed;
//clean the positions so the image remains within the viewer
if(imageContainer.x > 0) imageContainer.x = 0;
if(imageContainer.x < -viewer.width) imageContainer.x = -viewer.width;
if(imageContainer.y > 0) imageContainer.y = 0;
if(imageContainer.y < -viewer.height) imageContainer.y = -viewer.height;
}
"i want to put in this image a clickable spots in a specified x and y coordinated, and be able to click the spots": This also requires a little more thinking. In this case what you want to do is create [hotspots] on the image that are clickable, when clicked = do actions.
//USAGE
//define the click area coords
var clickCoords:Rectangle = new Rectangle();
clickCoords.x = 10; //starts at x 10
clickCoords.y = 10; //starts at y 10
clickCoords.width = 100; //100 wide
clickCoords.height = 100; //100 tall
//add the click listener
var clickArea:Sprite = hotSpot(imageContainer,clickCoords);
clickArea.addEventListener(MouseEvent.CLICK, onHotSoptClick);
//hot spot factory
function hotSpot(target:Sprite,coords:Rectangle):Sprite{
//create the hotspot
var hs:Sprite = new Sprite;
hs.graphics.beginFill(0,0);
hs.graphics.drawRect(0,0,coords.width,coords.height);
hs.graphics.endFill();
//add the hotspot to the target
hs.x = coords.x;
hs.y = coords.y;
target.addChild(hs);
}
function onHotSoptClick(e:MouseEvent):void{
//do something
}
IMPORTANT:
You may want to keep a list of hot spots you create so you can do garbage cleanup, and you plan on dynamically generating hotspots per image... then YOU MUST keep an active list of hot spots and remove when not in use.
You can catch the events MouseDown, MouseUp, MouseMove, MouseOut, on your viewing window, this way you can control exactly what do you want to do.
Here is the pseudo-code:
reset()
{
isDown=false;
downPointX=0;
downPointY=0;
distanceX=0;
distanceY=0;
}
onMouseDown()
{
isDown=true;
downPointX=mouseX;
downPointY=mouseY;
}
onMouseUp()
{
if(distanceX+distanceY==0 and isDown)
click(downPointX,downPointY);
reset();
}
onMouseMove()
{
if isDown then
distanceX=mouseX-downPointX;
distanceY=mouseY-downPointY;
drag(distanceX,distanceY);
endif;
}
onMouseOut()
{
reset();
}
drag(distanceX,distanceY)
{
change your map coordinates
}
click(downPointX,downPointY)
{
if(inSpot(downPointX,downPointY)==true)
changeMap();
endif;
}
changeMap()
{
change your maps and spots
}
avoid implementing any event for your spots sprites or you can get unexpected results.
You can check these for more information
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/Sprite.html#eventSummary

Make an object snap to another ojbect, then follow its path with pure ActionScript?

I am still trying to come to grips with how make an object snap to another ojbect, then follow its path with pure ActionScript (snap an arrow oject to a circle, then the circle follows the direct of the arrow when play button in hit).
Can somebody please help me with an small example so I can get my head round it, any help will be much appreciated. I am trying to create an application aimed towards something like this
http://itunes.apple.com/us/app/basketball-coachs-clipboard/id317785081?mt=8
I have got my drawing line working but do now know how to make the object follow the line, here is how I have drawn my line on the stage. Please could you give me a clue of how to do this.
function startPencilTool(e:MouseEvent):void
{
pencilDraw = new Shape();
board.addChild(pencilDraw);
pencilDraw.graphics.moveTo(mouseX, mouseY);
pencilDraw.graphics.lineStyle(shapeSize.width);
board.addEventListener(MouseEvent.MOUSE_MOVE, drawPencilTool);
}
function drawPencilTool(e:MouseEvent):void
{
pencilDraw.graphics.lineTo(mouseX, mouseY); /
}
function stopPencilTool(e:MouseEvent):void
{
board.removeEventListener(MouseEvent.MOUSE_MOVE, drawPencilTool);
}
1st
If you mean by "following its path", that the object follows another object, then simply do
obj2.x = obj1.x;
obj2.y = obj1.y;
to follow the exact coordinates. If you want to make some distance between them, then
obj2.x = obj1.x + dx;
obj2.y = obj1.y + dy;
choose dx and dy according to your wish.
2nd
If you want to make an app, where you can "draw an arrow" or "draw a path" and then an object should follow it, then you can try to store the coordinates of the mouse, while "drawing the arrow", then snap the object you want to these coordinates.
var coordinates:Array = [];
stage.addEventListener("mouseDown", md);
function md(evt:*):void
{
//empty the coordinates
coordinates = [];
//add listener when mouse is released
stage.addEventListener("mouseUp", mu);
//add a listener for enterframe to record the mouse's motion
addEventListener("enterFrame", recordMouse);
}
function mu(evt:*):void
{
stage.removeEventListener("mouseUp", mu);
removeEventListener("enterFrame", recordMouse);
//snap the object to the drawn line and play it
addEventListener("enterFrame", playRecording);
}
function recordMouse(evt:*):void
{
coordinates.push(new Point(stage.mouseX, stage.mouseY));
}
function playRecording(evt:*):void
{
//snap object to the recorded coordinates
myObject.x = coordinates[0].x;
myObject.y = coordinates[0].y;
//delete first element of array
coordinates.splice(0, 1);
//stop playing if there are no more points
if(coordinates.length == 0) removeEventListener("enterFrame", playRecording);
}
Place a movieclip on the stage and name it myObject. Then add the code and compile the swf.
Also, while "recoring" the coordinates, you can also draw some lines.
Change md function to this:
function md(evt:*):void
{
//empty the coordinates
coordinates = [];
//add listener when mouse is released
stage.addEventListener("mouseUp", mu);
//add a listener for enterframe to record the mouse's motion
addEventListener("enterFrame", recordMouse);
//clear graphics, and initialize line
with(graphics) clear(), lineStyle(1, 0xff0000), moveTo(stage.mouseX, stage.mouseY);
}
and recordmouse to this.
function recordMouse(evt:*):void
{
coordinates.push(new Point(stage.mouseX, stage.mouseY));
//draw the line
with(graphics) lineTo(stage.mouseX, stage.mouseY);
}
3rd
If you want to follow a pre-drawn line, then you have several options depending on your task. But everything depends on, how you exactly want to "snap" your object.