Articles Archive
Articles Search
Director Wiki
 

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/

Warren Ockrassa has been working with Director since 1992, and in 2001 authored "Director 8.5 Shockwave Studio: A Beginner's Guide" for Osborne/McGraw-Hill. When not trying to write the Next Great American Novel, he's still hacking code for the private sector.

Copyright 1997-2024, Director Online. Article content copyright by respective authors.