Dynamic Event Propogation
November 8, 2002
by Warren Ockrassa
Over the years Director has evolved from essentially an animation tool into something that is considerably more subtle and powerful -- effectively a complete programming environment.
In that time it's developed quite a set of quirks, and perhaps one of the more constant sources of frustration is that behaviors trap events, meaning that if you click one sprite that has a behavior attached to it, the click event stops with that sprite, even if you want other sprites beneath it to also detect and respond to the event.
The sample file below illustrates this. If you click the top sprite, you will only get a message from that top sprite, even if your click happens to occur in a place where the sprites intersect.
There are plenty of times when you might want to pass mouse events down a hierarchy of stacked sprites, but because of the way Director gets those events in a behavior, there's no way to do it. For example, using pass will have no effect at all.
So perhaps you might think of simply doing a sendSprite to tell any sprites "beneath" the current one that an event has happened and it needs to respond appropriately. Perhaps you have in mind something like this behavior, which looks for a sprite beneath it using a simple intersect test, and then transmits a click to that sprite whenever it (the upper sprite) receives it.
PROPERTY pnIntersectSprite
on beginSprite me
me.FindLowerSprite()
END beginSprite
on mouseUp me
me.TransmitClick()
END mouseUp
on FindLowerSprite me
nSprite = me.spriteNum - 1
rMyRect = sprite(me.spriteNum).rect
pnIntersectSprite = 0
repeat with nTest = nSprite down to 1
rTestRect = sprite(nTest).rect
rIntersect = intersect ( rMyRect, rTestRect )
if rIntersect <> rect ( 0, 0, 0, 0 ) then
pnIntersectSprite = nTest
exit repeat
end if
end repeat
END FindLowerSprite
on TransmitClick me
if pnIntersectSprite <> 0 then
sendSprite ( pnIntersectSprite, #mouseUp )
end if
END TransmitClick
This may seem at first to be a workable solution, but if you click the red rectangle in the following sample file, you'll see right away that there's a problem with it.
Did you see the trouble? Sprite 1 fires off its alert no matter where you click in sprite 2. In other words, even if you have not directly clicked sprite 1 at a point where its shape intersects with that of sprite 2, it still thinks you have.
This is where it gets tricky. For starters, you need to find out whether a given sprite actually intersects the one that has a propagation behavior on it; but you also need to determine if that lower sprite happens to be under the mouse when the event is propagated. If not, you don't send the event down the sprite stack.
PROPERTY pnIntersectSprite
on beginSprite me
me.FindLowerSprite()
END beginSprite
on mouseUp me
me.TransmitEvent( #mouseUp )
END mouseUp
on FindLowerSprite me
nSprite = me.spriteNum - 1
rMyRect = sprite(me.spriteNum).rect
pnIntersectSprite = 0
repeat with nTest = nSprite down to 1
rTestRect = sprite(nTest).rect
rIntersect = intersect ( rMyRect, rTestRect )
if rIntersect <> rect ( 0, 0, 0, 0 ) then
pnIntersectSprite = nTest
exit repeat
end if
end repeat
END FindLowerSprite
on TransmitEvent me, yEvent
if pnIntersectSprite <> 0 then
pMousePoint = the mouseLoc
rTestRect = sprite(pnIntersectSprite).rect
if pMousePoint.inside ( rTestRect ) then
sendSprite ( pnIntersectSprite, yEvent )
end if
end if
END TransmitEvent
The preceding behavior, because it determines whether the mouse is over the lower sprite when a click occurs, is going to give consistently more reliable results. The following sample file illustrates this in action.
But what about other sprites that may be further down the stack? Aren't we skipping them? That is, why not send the events down to all the sprites underneath?
More than anything else, time. Director lets you have up to 1000 sprites showing on a single frame. Imagine testing for and propagating an event down 999 times from one behavior.
In a case such as this it makes the most sense to attach this kind of behavior on a per-sprite, as-needed basis. That way each sprite becomes responsible for passing events down the stack, simply moving down to the next possible recipient sprite, rather than trying to keep it all under the command of a single "master" behavior.
But there are other events that can happen as well, such as mouseDown, mouseEnter, and so on; ignoring those is not a good idea. Adding a few parameters with getPropertyDescriptionList allows us to determine which events will get messages and which will not.
Finally we need to take into account visibility and motion. Since Director is an environment wherein animation can take place, sometimes sprites will overlap and sometimes they may not. For that reason we'd want to keep the intersect detection routine a bit more fluid, and we should probably add a parameter that lets us choose whether to send an event to a sprite that's been made invisible.
The following movie contains a more or less complete behavior that allows Director to determine dynamically whether a given sprite is intersecting both the current sprite and the mouse location; if it is, it sends whatever events it's been told to send along to the lower sprite.
The previous Director movie had a modified version of this behavior that assumes no animation will be taking place on your Stage; as such it would be a nice one to use that one for static applications, and this late dynamic one for more fluid applications.
Or, of course, you could simply add another parameter, letting you make the decision on your own, and thus keeping your code library maximally efficient. The final Director movie in this article contains code which does exactly that.
As you can see with this behavior, animation in the background doesn't cause it to malfunction. It's capable of intelligently recognizing when the black rectangle is intersecting the red one, and when it's not, and will respond appropriately.
These later movies all contain code that's a bit too lengthy to present here in toto, so the following code listing represents the final behavior as it appears in the last Director movie in this article.
PROPERTY pnIntersectSprite
PROPERTY pbPropagateMouseDown
PROPERTY pbPropagateMouseUp
PROPERTY pbPropagateMouseEnter
PROPERTY pbPropagateMouseLeave
PROPERTY pbPropagateMouseWithin
PROPERTY pbPropagateRightMouseDown
PROPERTY pbPropagateRightMouseUp
PROPERTY pbPropagateToInvisibleSprite
PROPERTY pbAssumeStaticStage
on beginSprite me
me.FindLowerSprite()
END beginSprite
on mouseDown me
if pbPropagateMouseDown = TRUE then
me.TransmitEvent( #mouseDown )
end if
END mouseDown
on mouseUp me
if pbPropagateMouseUp = TRUE then
me.TransmitEvent( #mouseUp )
end if
END mouseUp
on mouseEnter me
if pbPropagateMouseEnter = TRUE then
me.TransmitEvent( #mouseEnter )
end if
END mouseEnter
on mouseLeave me
if pbPropagateMouseLeave = TRUE then
me.TransmitEvent( #mouseLeave )
end if
END mouseLeave
on mouseWithin me
if pbPropagateMouseWithin = TRUE then
me.TransmitEvent( #mouseWithin )
end if
END mouseWithin
on rightMouseDown me
if pbPropagateRightMouseDown = TRUE then
me.TransmitEvent( #rightMouseDown )
end if
END rightMouseDown
on rightMouseUp me
if pbPropagateRightMouseUp = TRUE then
me.TransmitEvent( #rightMouseUp )
end if
END rightMouseUp
on FindLowerSprite me
pnIntersectSprite = 0
if pbAssumeStaticStage = TRUE then
nSprite = me.spriteNum - 1
rMyRect = sprite(me.spriteNum).rect
repeat with nTest = nSprite down to 1
rTestRect = sprite(nTest).rect
rIntersect = intersect ( rMyRect, rTestRect )
if rIntersect <> rect ( 0, 0, 0, 0 ) then
pnIntersectSprite = nTest
exit repeat
end if
end repeat
end if
END FindLowerSprite
on TransmitEvent me, yEvent
pMousePoint = the mouseLoc
if pnIntersectSprite <> 0 then
rTestRect = sprite(pnIntersectSprite).rect
if pMousePoint.inside ( rTestRect ) then
if sprite(pnIntersectSprite).visible = TRUE or (sprite (pnIntersectSprite).visible = FALSE and pbPropagateToInvisibleSprite = TRUE ) then
sendSprite ( pnIntersectSprite, yEvent )
end if
end if
else if pnIntersectSprite = 0 and pbAssumeStaticStage = FALSE then
bFoundIntersect = FALSE
nSprite = me.spriteNum - 1
rMyRect = sprite(me.spriteNum).rect
repeat with nTest = nSprite down to 1
rTestRect = sprite(nTest).rect
rIntersect = intersect ( rMyRect, rTestRect )
if rIntersect <> rect ( 0, 0, 0, 0 ) then
bFoundIntersect = TRUE
exit repeat
end if
end repeat
if bFoundIntersect = TRUE then
rTestRect = sprite(nTest).rect
if pMousePoint.inside ( rTestRect ) then
if sprite(nTest).visible = TRUE or ( sprite (nTest).visible = FALSE and pbPropagateToInvisibleSprite = TRUE ) then
sendSprite ( nTest, yEvent )
end if
end if
end if
end if
END TransmitEvent
on getPropertyDescriptionList me
if the currentSpriteNum > 0 then
lMyPropList = [:]
lMyPropList.addProp ( #pbPropagateMouseDown, [#comment: "Propagate mouseDown?", #format: #boolean, #default: FALSE] )
lMyPropList.addProp ( #pbPropagateMouseUp, [#comment: "Propagate mouseUp?", #format: #boolean, #default: TRUE] )
lMyPropList.addProp ( #pbPropagateMouseEnter, [#comment: "Propagate mouseEnter?", #format: #boolean, #default: FALSE] )
lMyPropList.addProp ( #pbPropagateMouseLeave, [#comment: "Propagate mouseLeave?", #format: #boolean, #default: FALSE] )
lMyPropList.addProp ( #pbPropagateMouseWithin, [#comment: "Propagate mouseWithin?", #format: #boolean, #default: FALSE] )
lMyPropList.addProp ( #pbPropagateRightMouseDown, [#comment: "Propagate rightMouseDown?", #format: #boolean, #default: FALSE] )
lMyPropList.addProp ( #pbPropagateRightMouseUp, [#comment: "Propagate rightMouseUp?", #format: #boolean, #default: FALSE] )
lMyPropList.addProp ( #pbPropagateToInvisibleSprite, [#comment: "Propagate events to nonvisible sprite?", #format: #boolean, #default: FALSE] )
lMyPropList.addProp ( #pbAssumeStaticStage, [#comment: "Sprites will not animate behind this sprite", #format: #boolean, #default: TRUE] )
end if
return lMyPropList
END getPropertyDescriptionList
Download all of the sample moveis in Director 8.5 format as ZIP or SIT archives.
All colorized Lingo code samples have been processed by Dave Mennenoh's brilliant HTMLingo Xtra, available from his site at http://www.crackconspiracy.com/~davem/
Copyright 1997-2024, Director Online. Article content copyright by respective authors.