Imagine a painting program. I have two imagens overlaping each other. I must be able to click on the image behind if I click on a transparent part of the above one.
I add an event listener on each image. So I must prevent the first one to dispatch click event in order to the behind one dispatch it.
(I mean, I already check for transparent pixels, but i can't cancel that event to the other imagem dispatch it.)
There is some dirty solution. Say your "images" are encapsulated in Sprites, otherwise you can't attach a listener to a Bitmap object or a Shape object. MovieClips - well, should still work, although checking alpha is a lot harder. You have your listeners attached to those sprites. First, that sprite in itself can check transparency, and if not transparent, proceed the event. To get what's behind, you can call stage.getObjectsUnderPoint(), this will return an Array of DisplayObjects, with foreground being on top. So you might have one single listener that would call this to enumerate what's under the point, then do as Roman Trofimov advises to determine that object's transparency. Once you'll find a non-transparent object, you do:
// "b" is a non-transparent DisplayObject found beforehand
while (b && (!(b is InteractiveObject))) b = b.parent;
if (b) {
var p:Point = new Point(e.stageX, e.stageY);
p = b.globalToLocal(p);
b.dispatchEvent(new MouseEvent('click', true, true, p.x, p.y));
}
This will dispatch a click event to the object that's able to receive events, and is opaque enough to obscure the background.
The MovieClip property 'mouseEnabled' can prevent click events from triggering on objects in front of other objects, for example, take two movie clips of the same size and same position:
mc1.addEventListener(MouseEvent.CLICK, click1);
function click1(e:MouseEvent) : void
{
trace("1");
}
mc2.addEventListener(MouseEvent.CLICK, click2);
function click2(e:MouseEvent) : void
{
trace("2");
}
mc1.mouseEnabled = false;
Output would be "2" as the click would essentially go 'through' mc1.
In flex you can add mouseEnabledWhereTransparent="false" to components. Not fully sure if that variable is available to you though
Generally, you will always get first event from first sprite (if it was not capture phase ot priority change). But as I understand, you need to check was it transparent and if not - stop bubbling this event.
To stop event - use event.stopImmediatePropagation() in event handler.
To determine transparency:
1) If it is just only the one bitmap, you can check its .bitmapData with method .getPixel32, it returns you alpha value.
2) If it is combined sprite (vector and bitmap), you need to manually render it to Bitmap using BitmapData.draw and then use solution from second case
3) To understand if it was absolutelly miss click, you can try this method: Check transparency
Related
I'm not even sure if this is a problem that can be solved through code. I have simple movie clips in an array that I'm trying to add click Event Listeners to, and I can change the buttonMode to true and add the event Listener, but only one of the movieclips actually shows the behavioral changes from the buttonMode and event Listener.
for(var d:int = 0; d < doors.length; d++)
{
doors[d].buttonMode = true;
doors[d].addEventListener(MouseEvent.CLICK, doorClick);
trace(doors[d].buttonMode);
trace(doors[d].hasEventListener(MouseEvent.CLICK));
}
all the traces return true, and I traced d and doors[d] to make sure that the problem wasn't with the array, but it isn't and only the door at index 1 works as intended. How can I find why the listeners aren't working?
Isolate your doors and loop. The code will work on its own without other elements. I've had this same problem before and it's usually because the invisible portion of another movieclip is overlaying the button objects.
If you're adding other MovieClips dynamically that may overlay the doors, locate the container movieclip of those objects and set its properties mouseEnabled and mouseChildren to false. The container and its child objects will no longer receive mouse clicks instead of your door movieclips.
The answer here clarified the issue for me:
https://stackoverflow.com/a/2686510/629407
I've a problem with ROLL_OVER event listener. When I enter the empty area withing the movieclip with mouse cursor, ROLL_OVER event triggers. But I want that event trigger only when mouse cursor is on the colored area.
To Make it more clear: Think about " O " letter, when mouse cursor is between the empty area of O letter (inside of O) , event shouldn't trigger. It should trigger only when mouse curser is on the black area.
How can I implement this?
Thanks
-Ozan
PROBLEM IS SOLVED THANKS TO #Ethan Kennerly
I just want to add a few things to help people have problem same as me. In my situation I tried to make continents glow when my mouse is over them. I used the ROLL_OVER/MOUSE_OVER eventlistener to check if my mouse is over them or not. But with the data given by Ethan Kennerly I produced another way.
In Ethan Kennerly's solution, if your mouse enters the area of continent from a transparent area , it doesn't get blur effect because ROLL_OVER and MOUSE_OVER event listeners only trigger once per enters so I used MOUSE_MOVE event listener on each continent movieclips.
And for this statement:
if (isPixelTransparent(DisplayObject(event.currentTarget), new Point(stage.mouseX, stage.mouseY)) {
return;
}
add whatever is in the "ROLL_OUT or MOUSE_OUT" eventlistener function, add all of them inside this statement. But don't remove ROLL_OUT or MOUSE_OUT functions.
It sounds like the movie clip contains a shape that has transparent pixels. Transparent pixels respond to mouse over and roll over. If you could draw vector graphics that have no shapes with transparent pixels, the mouse would ignore the empty space in the movie clip's bounding box.
Yet it sounds like you need to use transparent pixels and you want the mouse to ignore them, so you could guard, like this:
private function onRollOver(event:MouseEvent):void
{
if (isPixelTransparent(DisplayObject(event.currentTarget), new Point(stage.mouseX, stage.mouseY)) {
return;
}
// respond to roll over.
}
To detect transparency, Miguel Santirso rendered the pixels and translated the coordinate space here: http://sourcecookbook.com/en/recipes/97/check-if-a-pixel-is-transparent-in-a-displayobject (Except line 38 looks on my computer like "rect" got rendered as "ct"). You could optimize that code by only drawing the pixel in question, instead of the whole image, and checking if that pixel value (getPixel32) is 0, instead of calling a hitTest. I would optimize Miguel's code like this:
public static function isPixelTransparent(objectOnStage:DisplayObject, globalPoint:Point):Boolean
{
var local:Point = objectOnStage.globalToLocal(globalPoint);
var matrix:Matrix = new Matrix();
matrix.translate(-local.x, -local.y);
var data:BitmapData = new BitmapData(1, 1, true, 0x00000000);
data.draw(object, matrix);
return 0x00000000 == data.getPixel32(0, 0);
}
By the way, if all your movie clips would have the same hit test shape, you could create a separate transparent shape that listens to the roll over. I use a transparent shape to define a custom hit test shape that is a consistent and simple shape (like a circle) when the image is a more complicate shape (like an X or an O with nothing in the middle). The custom hit test shape is a Sprite with a transparent shape. The sprite listens to the roll over. A separate mouse listener shape is also useful if your movie clip, on later frames, creates new shapes that alter the silhouette of the movie clip.
The easiest solution would be using the Interactive PNG class by Moses.
http://blog.mosessupposes.com/?p=40
Normally the clear areas of a PNG are treated as solid, which can be especially frustrating when dealing with a lot of images that overlap each other because they tend to block mouse interactions on the clips below them.
This utility fixes that so that mouse events don't occur until you
bump against a solid pixel, or a pixel of any transparency value
besides totally clear. InteractivePNG lets you set an alphaTolerance
level to determine what transparency level will register as a hit.
I've been searching for the past few hours how to do this but with no luck. First of all I'm new to AS and not sure what to search for.
So here's what I have and what I want to do:The stage has only 1 frame and on the stage I have 1 button (b1), 1 movie clip that is not visible (area) and 1 visible movie clip that is an animation of 20 frames(ani). For the moment when I press button "b1" it will start the movie "ani" (movie is stopped initially) that will stop when it reaches frame 20. Now what I want is when it reaches the last frame to make movie clip "area" visible. Since I am inside "ani" and on frame 20, I cannot use directly area.visible = true; as I would get the error "Access of unidentified property area." What would be the way to access "area"'s properties from within the other object ?
Inside your "ani" MovieClip (at last frame) add the following sentence,
MovieClip(this.parent).area.visible = true;
Here, parent is your main timeline.
(Note: This approach is not recommended).
Instead, use External classes approach. e.g. use Loader class to load animated swf with COMPLETE event and with contentLoaderInfo get swf object and detect for last frame and make area MovieClip visible.
I'm not sure I completely understand everything you said, but I think what you want to do is something like this on your stage where both ani and area have scope :
ani.addEventListener(Event.ENTER_FRAME, frameCheck);
function frameCheck(e:Event):void
{
if (ani.currentFrame == ani.totalFrames)
{
ani.removeEventListener(Event.ENTER_FRAME, frameCheck);
area.visible = true;
}
}
That was just an example of how you could detect ani hitting the last frame and dealing with that appropriately.
You would need to add the event listener EACH TIME the button is pressed.
I have this problem, I use this image pan class: http://www.lextalkington.com/blog/2009/08/auto-pan-class-for-panning-an-image-on-mouse-movement/
but the problem is that the objects/sprites/movieclips that are in it have to be clickable, only problem is that the mouseChildren adn mouseEnabled properties can't be applied to a Rectangle object.
Anyone has an idea on how to be able to click through this so I can acces my objects in the panned item? (if that makes any sense...)
This class is using a Rectangle as the scrollRect for the image. The scrollRect only specifies the visible area of the image. It is not the thing you want to detect mouse clicks on.
Instead, you can listen for a mouse click on the image itself.
From the code you linked to, the image is a DisplayObject variable named _clip.
In the constructor for that image panning class, you can add your mouse listener:
_clip.addEventListener(MouseEvent.CLICK, onImageClick);
Then define the event handler:
private function onImageClick(event:Event):void
{
// do something
}
By the way, since _clip is a DisplayObject, it doesn't have mouseChildren or mouseEnabled properties (those are defined in subclasses of DisplayObject).
_clip.mouseEnable = false;
this should work, considering tha _clip will be clicked through
Say I have the following;
public function onBellyPatch_Two(e:MouseEvent):void
{
inBelly_Two.visible = true;
}
inBelly_Two is a MovieClip
I have two movieclips over top of each other, when you click one MovieClip another one shows up on top, and when you click that (second MovieClip)a textBox is updated.
I noticed that even if a movieclip object's visible property is false, when you click in the area where the movie clip is the MouseEvent.CLICK event is called. Is there a way to get around this? I would like to stack movieClip.
I guess one way to get around this problem would be:
removing the eventListener when movieClip is not visible and enabling the eventListener when moviclip is visible.
Is there some otherway?
Much Thanks,
Mike
Try adding:
inBelly_Two.buttonMode = false;
This will let onBellyPatch_Two be called no matter if inBelly_Two is visible or not.
Instead of removing the listener, you can just say
mc.mouseEnabled = false;
mouseEnabled docs