Aug 05, 2008

Ensnaring mouseEnabled

A lot of what we do here at the Spaceship involves complex SWFs loaded in and layered on top of other SWFs. We wind up with a lot of buttons nested underneath of movieclips nested inside of SWFs.

When we got to handling mouse interaction in AS2, this was fine so long as a button wasn't layered on top of another button. But in AS3 it's a different ball of wax -- each DisplayObject has a mouseEnabled property, which is set to true by default. mouseEnabled means that the DisplayObject in question *can* react to mouse events.

This can be lead to some tricky and irritating issues. Hit the jump for specifically what is happening and how to fix it.


The rule of thumb is more or less this:

When a mouse event happens, the DisplayObject underneath the mouse at the highest depth with mouseEnabled set to true will fire a MouseEvent, which will relay to it's parent, and it will go all the way down to the stage.

Consider this: MovieClip A is on the topmost layer. MovieClip B has a MouseEvent.CLICK listener. MovieClip A overlaps MovieClip B.

Any time a user clicks on MovieClip A it will hear the MouseEvent and sending it down the chain... MovieClip B would never hear it. This effectively creates the "I'm listening for the mouse but nothing happens for no reason" bug. By setting MovieClip A to mouseEnabled=false, all is well. Note that if MovieClip A had anything nested inside of it, they too would have to be disabled. You can do this either with mouseEnabled=false on each of them painstakingly or by setting MovieClip A's mouseChildren=false.

So okay! We know what the problem is... a MovieClip can block the mouse from another movieclip. But most of what we're building in Flash is far more complex than one MovieClip on top of another... is there an easy way to find the culprit of mouse blocking? Indeed there is! Sort of. Just drop this code on your main timeline (or your root class, if you prefer):


stage.addEventListener(MouseEvent.CLICK,_checkMouseEventTrail,false,0,true);
function _checkMouseEventTrail($e:MouseEvent):void
{
var p:* = $e.target;
while(p)
{
trace(">>", p.name,": ",p);
p = p.parent;
}
};


What we're doing in the above is listening to the stage for any mouse interaction and then outputting the path on the DisplayList from the origin of the event all the way down to the stage.

We use this as a utility to help us figure out what DisplayObject is blocking our way to the DisplayObjects we actually care to listen to.

Share this Post


                           

Comments


Adnan Babar     Feb 01, 2009
Hi

Thank you so much for this useful post I was really looking for the solution for this and here I got it.

thanks again

PS: I love the work that you guys are doing at Spaceship.

Jon     Jan 14, 2009
Zevan Rosser wrote another good piece about this here
http://www.learningactionscript3.com/2007/11/10/event-phases-in-action/

derkoidus     Dec 18, 2008
anyone have a clue on how to do this in actionscript 2?

Aleksandar Andreev     Sep 15, 2008
Recently I made a library that solves such issue http://labs.wispagency.com/?p=15 , of course there are always alternatives like getObjectsUnderPoint but it is always good to be aware more different ways of solving a problem.

Christian Kragh     Sep 04, 2008
Hey Jamie.
I think this is a good post; helpful for many Flashers out there hopefully, discovering this pesty issue. I've gotten into the habit of making a separate "hitArea" movie clip without any other objects in it to act as the movie clip receiving the events and listeners. Also catching myself saying "oh, god dammit" to myself and quickly typing in mouseEnabled and mouseChildren to false. ;)
Christian

zahir     Sep 01, 2008
on a mac
[control-option-command-8]
switches your display to black on white.



John     Aug 21, 2008
Hi. I love your blog, it's the best, but... It burns my retina, the black with white text. Styleshifter would be nice. :)

Jamie Kosoy     Aug 06, 2008
@Philipp: Yes you're right. I'll update that. Good catch!

@mailiboo: getObjectsUnderPoint() could work too, absolutely. I don't know if there's a performance benefit one way or the other -- I typically just throw the stage listener in for the duration of my debugging and then remove it anyway. Different ways of skinning a cat, I suppose.

maliboo     Aug 06, 2008
What about Sprite::getObjectsUnderPoint() + some filtering on mouseEnabled?

Philipp     Aug 06, 2008
"by setting MovieClip A’s mouseChildren property to true."

i think it should say "setting ... mouseChildren property to FALSE.", isn't it.

phil

Jamie Kosoy     Aug 05, 2008
@Jeffery: Surely, but neither target nor currentTarget zeroes in on where the rogue DisplayObject starts -- what SWF it lives in, for example. This loop might seem excessive, but its super helpful finding the root of the problem, literally. :)

Jeffery     Aug 05, 2008
Another way to do this would be to snag the currentTarget property of the MouseEvent.CLICK object.


Speak






Submit »